From: Robert Klemme Date: 2013-02-07T06:31:46+09:00 Subject: Re: Implementing DRY with a function call On Wed, Feb 6, 2013 at 9:14 PM, Rob Marshall wrote: > To Robert...yes, I have NOT, yet, grasped the concept of blocks :-) OK. First of all a block is just an anonymous function. Because it doesn't have a name it is invoked via keyword "yield" as you are already aware. You can see it from the stack trace: irb(main):001:0> def f; yield; end => nil irb(main):002:0> puts f { caller(0) } (irb):3:in `block in irb_binding' (irb):1:in `f' (irb):3:in `irb_binding' ... An alternative way to invoke the block is via an explicit last argument: irb(main):007:0> def f(&any_name) any_name.call end => nil irb(main):008:0> puts f { caller(0) } (irb):8:in `block in irb_binding' (irb):7:in `call' (irb):7:in `f' (irb):8:in `irb_binding' ... Also, a block is scoped in the *calling* scope. So it has access to all variables defined there: irb(main):005:0* def g; x=1; f { puts x } end => nil irb(main):006:0> g 1 => nil > Let me explain: I'm using FFI to access a library. The way the library > calls work is that none of them return errors or even raise exceptions. > The expectation is, that after calling a function, the user will check > (via a function call) for errors. So what I end up with is: > > Module MyModule > class SubClass > def initialize(instance_var) > @instance_var = instance_var > end > > def my_function_call(arg1,arg2) > unless @instance_var.nil? > ret = MyModule.Real_FunctionCall(@instance_var,arg1,arg2) > if MyModule.CheckLastError != 0 > raise MyException, MyModule.GetLastErrorText > end > return ret > else > raise ArgumentError, "Instance variable missing" > end > end > > def my_other_call(arg1,arg2, arg3) > ret = MyModule.Real_OtherCall(arg1,arg2,arg3) > if MyModule.CheckLastError != 0 > raise MyException, MyModule.GetLastErrorText > end > return ret > end > end > end Here's a possible implementation idea: # untested def my_function_call(arg1,arg2) invoke_fun(:Real_FunctionCall, [arg1, arg2], %w{@instance_var}) end def my_other_call(arg1,arg2, arg3) invoke_fun(:Real_OtherCall, [arg1, arg2, arg3]) end def fun_tastic(arg1,arg2) invoke_fun(:Real_FunctionCall, [arg1, arg2], %w{@length @size}) end private def invoke_fun(name, args, ivars = []) ivals = ivars.map {|iv| instance_variable_get(iv).tap {|val| raise ArgumentError, "Ivar #{iv} missing" if val.nil?}} MyModule.send(name, *ivals, *args).tap {raise MyException, MyModule.GetLastErrorText unless MyModule.CheckLastError == 0} end Maybe this calls for meta programming. You could define a method on class level which will define instance methods based on these inputs - name of the method to invoke - number of additional arguments - list of instance variables to use (possibly empty) The generated code would behave like invoke_fun only that it would have "burned" method name and instance variable names into it. > What I think will work is (here's a simplified example): I don't think this is such a good idea. You want to get rid of as much repetition as possible. That's what I did with the code above. Since the structure of the methods is so identical you can automate much more. Once you found out what your inputs are (in terms of variants of method implementations) things fall into place pretty naturally. > def check_error(*args) > if args.length > 0 > args.each_with_index { |v,i| printf "%2d argument value: %s\n", i, v > } > end > ret = yield > puts "Check error here" > ret > end > > check_error("this","is","a","test"){ return_value("something again") } As I said: return_value is completely superfluous. The code should sit in check_error method because that avoids typing return_value repetitively. > The part I didn't understand is how Ruby will allow "you" to put a block > after the args in parens...Once I figured that out (thanks to: > http://blog.sidu.in/2007/11/ruby-blocks-gotchas.html#.URK0k-j6bYA) it > all made sense :-) I am not sure I understand what you mean by "put a block after the args in parens". > Thanks for the help, You're welcome! Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/