Hi,
I'm working on my first Ruby project...and I'm trying to adhere to the
DRY principle. What I have is a lot, and I mean a LOT, of repetition.
The calls all look similar to this (by the way I'm writing an interface
to a library with FFI):
module MyLibrary
extend FFI::Library
ffi_lib [MyLibrary]
attach_function :LibFunc, [ :string, :pointer ], :void
attach_function :CheckError, [], :int
class SubClass
def lib_func(name)
ret = FFI::MemoryPointer.new(:char,256)
MyLibrary.LibFunc(name,ret)
if MyLibrary.CheckError != 0
raise "You got an error"
end
end
end
end
That's the basic idea, but I have several classes within the module,
each with a dozen, or more, functions. What I am doing now is, within
the module:
def call_check_error(function)
ret = lambda {function}.call
if MyLibrary.CheckError != 0
raise "You got an error"
end
return ret
end
For functions that don't actually return a value, ret is nil, so I can
just ignore it when I don't need it, and that way I avoid having to have
two methods, one with, and one without, a return. So that in lib_func I
do:
def lib_func(name)
ret = FFI::MemoryPointer.new(:char, 256)
MyLibrary.call_check_error(MyLibrary.LibFunc(name,ret))
end
This seems to be working fine, although I've just started doing it this
way.
So my question is: Is that a good way to implement this? I'm assuming
it's probably not the best way, and I would hope someone with more
experience with Ruby might have a suggestion. It kind of feels like:
lambda {function}.call
is a bit hokey, but I wasn't sure how else to do it.
Thanks,
Rob
on 2013-02-05 21:15
on 2013-02-06 11:51
On Tue, Feb 5, 2013 at 9:15 PM, Rob Marshall <lists@ruby-forum.com> wrote: > > end > raise "You got an error" > ret = FFI::MemoryPointer.new(:char, 256) > MyLibrary.call_check_error(MyLibrary.LibFunc(name,ret)) > end This is not doing what you think it does. Ruby evaluates the arguments of a method before calling the method. So that that line you are actually calling the MyLibrary.LibFunc, getting its return value and passing it to call_check_error. Say for example that LibFunc returns "a", you are passing that string "a" to the call_check_error, so that you do: lambda {"a"}.call, which also returns "a". > This seems to be working fine, although I've just started doing it this > way. > > So my question is: Is that a good way to implement this? I'm assuming > it's probably not the best way, and I would hope someone with more > experience with Ruby might have a suggestion. It kind of feels like: > > lambda {function}.call > > is a bit hokey, but I wasn't sure how else to do it. If I remember correctly, you asked some time ago a way to refactor this and Robert Klemme suggested to use blocks. So something like: def call_with_check_error ret = yield if MyLibrary.CheckError != 0 raise "You got an error" end return ret end def lib_func(name) pointer = FFI::MemoryPointer.new(:char, 256) call_with_check_error { MyLibrary.LibFunc(name,ret) } end Hope this helps, Jesus.
on 2013-02-06 16:27
Hi Jesus,
Thanks for the help. I think I see your point about the "lambda {}call"
simply returning the already evaluated expression, as opposed to
actually evaluating it at the time of the yield. But in my case it
doesn't really matter. Where the expression gets evaluated is less
important than making sure I check for errors immediately afterward. So,
from my perspective (and in this case) I am getting the "correct"
result. Here's what I've tried in irb (removing the prompt):
def use_yield
ret = yield
puts "This is where I check for errors"
return ret
end
def use_lambda(func)
ret = lambda {func}.call
puts "This is where I check for errors"
return ret
end
def return_value(val)
return "This is the returning value: #{val}"
end
use_yield { return_value("who cares?") }
This is where I check for errors
=> "This is the returning value: who cares?"
use_lambda(return_value("who cares?"))
This is where I check for errors
=> "This is the returning value: who cares?"
Again, they both seem to be doing the exact same thing. And in my module
it is working correctly, giving me the results I want.
Plus I would like to add a slight wrinkle, i.e. the ability to pass in
additional arguments. The yield method doesn't seem to allow me to pass
in anything but a block and I don't seem able to set local variables,
etc. With the "lambda {}.call" I can pass a variable argument list and
do additional testing on the items in the list as well as execute the
function (the last item on the argument list). Or is there some way to
pass/initialize additional arguments in a block that will get evaluated
by the yield?
For example:
def use_lambda(*list)
if list.length > 1
list[0..-2].each_with_index {|v,i| puts "This is argument #{i}:
#{v}"}
list.slice!(0..-2)
end
ret = lambda { list[0] }.call
puts "This is where I test for errors"
return ret
end
Doing that I can pass as many additional arguments as I want and
evaluate them etc. I haven't figured out how to do that with the
block/yield method.
Thanks,
Rob
on 2013-02-06 16:57
On Wed, Feb 6, 2013 at 4:27 PM, Rob Marshall <lists@ruby-forum.com> wrote: > Hi Jesus, > > Thanks for the help. I think I see your point about the "lambda {}call" > simply returning the already evaluated expression, as opposed to > actually evaluating it at the time of the yield. But in my case it > doesn't really matter. > Where the expression gets evaluated is less > important than making sure I check for errors immediately afterward. Unless the expression raises an exception. Passing the result of the call means that the call is perform at a point where no exception handling is being done, while if you pass a block, you can execute it in a controlled place, where you can catch excepctions and handle them. > def use_lambda(func) > This is where I check for errors > additional arguments. The yield method doesn't seem to allow me to pass > if list.length > 1 > evaluate them etc. I haven't figured out how to do that with the > block/yield method. You can pass a variable argument list to a block too: 1.9.2p290 :001 > def yield_args *args 1.9.2p290 :002?> yield *args 1.9.2p290 :003?> end => nil 1.9.2p290 :004 > yield_args(1,2,3,4) {|a,b,c,d| p a,b,c,d} 1 2 3 4 => [1, 2, 3, 4] 1.9.2p290 :005 > yield_args(1,2,3,4) {|*args| p args} [1, 2, 3, 4] => [1, 2, 3, 4] Jesus.
on 2013-02-06 17:51
On Wed, Feb 6, 2013 at 4:27 PM, Rob Marshall <lists@ruby-forum.com> wrote: > Hi Jesus, > > Thanks for the help. I think I see your point about the "lambda {}call" > simply returning the already evaluated expression, as opposed to > actually evaluating it at the time of the yield. But in my case it > doesn't really matter. Where the expression gets evaluated is less > important than making sure I check for errors immediately afterward. I agree to what Jesus said: the block allows more control. For example you can also allocate resources and ensure they are cleaned up properly after evaluating the block. I am not 100% sure yet I understood your use case because I haven't seen the identical pattern (as far as I can see you only posted one example which makes it difficult for others to detect what your multiple identical pieces of code have in common and what not). Can we maybe start over and see like 5 examples of these repeated calls? Or get a more general explanation (i.e. in English and not code) what repeated steps there are which are repeated. > def return_value(val) > return "This is the returning value: #{val}" > end What do you need this for? > use_yield { return_value("who cares?") } > This is where I check for errors > => "This is the returning value: who cares?" > > use_lambda(return_value("who cares?")) > This is where I check for errors > => "This is the returning value: who cares?" If you use the block form you can integrate the return_value logic there and do not need to explicitly invoke that method. > Plus I would like to add a slight wrinkle, i.e. the ability to pass in > additional arguments. The yield method doesn't seem to allow me to pass > in anything but a block and I don't seem able to set local variables, > etc. With the "lambda {}.call" I can pass a variable argument list and > do additional testing on the items in the list as well as execute the > function (the last item on the argument list). Or is there some way to > pass/initialize additional arguments in a block that will get evaluated > by the yield? Yes, see Jesus example. > For example: > > def use_lambda(*list) > if list.length > 1 > list[0..-2].each_with_index {|v,i| puts "This is argument #{i}: > #{v}"} > list.slice!(0..-2) > end > ret = lambda { list[0] }.call Sorry, but this is nonsense - it's the same as ret = list[0] and you are wasting a few CPU cycles. Example: irb(main):001:0> list = %w{foo bar} => ["foo", "bar"] irb(main):002:0> lambda { list[0] }.call => "foo" I get the impression that you didn't yet fully get the hang of blocks. :-) > puts "This is where I test for errors" > return ret > end > > Doing that I can pass as many additional arguments as I want and > evaluate them etc. I haven't figured out how to do that with the > block/yield method. You do not even need to pass arguments to the /method/ to work with them in the block: def checked ret = yield raise "Foo" unless ret == 0 ret end def foo(a, b) checked do a + b end end Kind regards robert
on 2013-02-06 21:14
To Robert...yes, I have NOT, yet, grasped the concept of blocks :-)
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
The difference is that I have many classes in the module and LOTS of
functions within each class. As you can see, in some cases I want to
check an instance variable before making the call (because the the
function call will need it), and in other cases I don't need to. But in
ALL cases I need to do the error check. With close to a hundred+
functions, that's 300+ repetitious lines of code.
What I think will work is (here's a simplified example):
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") }
0 argument value: this
1 argument value: is
2 argument value: a
3 argument value: test
Check error here
=> "This is the returning value: something again"
check_error { return_value("something again") }
Check error here
=> "This is the returning value: something again"
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.ht...) it
all made sense :-)
Thanks for the help,
Rob
on 2013-02-06 22:32
On Wed, Feb 6, 2013 at 9:14 PM, Rob Marshall <lists@ruby-forum.com> 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 > > 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. > 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.ht...) 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
on 2013-02-07 01:39
Hi Robert,
At present I'm using the approach I mentioned before, but let me try and
explain my confusion...
Every other language I've worked in, C, Perl, Python, Java...the way you
call a function with arguments is: func(arg1, arg2, arg3). In Python you
can have variable args and keyword args specified as (*args, **kwargs),
but in general you define the arguments that a function expects, and
then pass those arguments between parentheses. So the idea that I could
do:
func(arg1,arg2,arg3) { block }
And a) Not get a syntax error, and b) Be able to define the parameters
for the function as:
def func(*args)
yield
end
and not only get the args specified between the parens in *args, but
also still pass the block and "yield" it, is a very new, and foreign,
concept for me.
What I find a bit confusing about Ruby is that, given:
def func(*args)
args.each { |a| puts a }
yield
end
Then do the following:
>> func "this", "is", "a", "test", return_value("something")
this
is
a
test
This is the returning value: something
LocalJumpError: no block given
from (irb):108:in `func'
from (irb):110
This one is sort-of obvious since the function return_value (which is
just a dummy function that simply shows calling a function with some
arbitrary value and returning it slightly modified...so don't get hung
up on it) is evaluated prior to being passed as an argument to func. But
since there's no block, I get the LocalJumpError.
>> func "this", "is", "a", "test", { return_value("something") }
SyntaxError: compile error
(irb):111: odd number list for Hash
from (irb):111
This is is also somewhat obvious because it isn't treating the {} as
defining a block but attempting to define a hash...That's because I
haven't "formally" defined the arguments, so the interpreter assumes
everything is supposed to be an argument and not a block.
>> func("this", "is", "a", "test") { return_value("something") }
this
is
a
test
=> "This is the returning value: something"
This is still the odd one for me that now (sort-of) makes sense but
would NOT be allowed in any language I've used in the past. So it's
going to take some getting used to...
Rob
on 2013-02-07 04:22
On 7 February 2013 10:39, Rob Marshall <lists@ruby-forum.com> wrote: > Every other language I've worked in, C, Perl, Python, Java...the way you > call a function with arguments is: func(arg1, arg2, arg3). In Python you > can have variable args and keyword args specified as (*args, **kwargs), > but in general you define the arguments that a function expects, and > then pass those arguments between parentheses. Give or take a few other var-args tricks. Remember C has stdarg.h and allows function signatures like: void func(...); And Java has special array syntactic sugar: public static void func(Object... args) And PHP almost completely ignores the variables named in the function signature, because you can always use func_num_args() and func_get_args() etc. Ruby allows non-parenthetical args, the same as shell script (e.g. Bash). Technically perl doesn't require parentheses either, except that functions in perl expect a list of parameters, and perl lists use parentheses, etc. etc. Upshot: `$thingy->foo` is also a function call in perl. > end > > and not only get the args specified between the parens in *args, but > also still pass the block and "yield" it, is a very new, and foreign, > concept for me. > If it helps, you can also define it as: def func(*args, &a_block) if you want to make explicit the fact that there is (or expects to be) a block. It also gives you direct access to the block, so you can poke it and stuff. > this > arbitrary value and returning it slightly modified...so don't get hung > up on it) is evaluated prior to being passed as an argument to func. But > since there's no block, I get the LocalJumpError. > To the first remark: this is the same as any other programming language. For example in Java: func("this", "is", "a", "test", return_value("something")); ...will execute the 'return_value' function first, then pass its result as the final parameter to 'func'. As an aside to the second: You can get around/preempt the LocalJumpError by wrapping `yield` thus: def func(*args) args.each { |a| puts a } yield if block_given? end Not necessarily what you want to do here, but handy to know. >> func "this", "is", "a", "test", { return_value("something") } > SyntaxError: compile error > (irb):111: odd number list for Hash > from (irb):111 > > This is is also somewhat obvious because it isn't treating the {} as > defining a block but attempting to define a hash...That's because I > haven't "formally" defined the arguments, so the interpreter assumes > everything is supposed to be an argument and not a block. > That would happen irrespective of how you define the function parameters. SyntaxErrors happen when ruby is parsing and interpreting the file, not when it's executing. A curly brace that follows a comma is _always_ interpreted as the start of a Hash literal. Since blocks are special, "out-of-band" parameters, you don't need a comma to distinguish them from the other args. However, since curly-brace block syntax looks a lot like Hash literal syntax, you need to give the parser a bit more help in this case (i.e. by explicitly wrapping the "real" args in parens, then giving the block.) > >> func("this", "is", "a", "test") { return_value("something") } > this > is > a > test > => "This is the returning value: something" > > This is still the odd one for me that now (sort-of) makes sense but > would NOT be allowed in any language I've used in the past. So it's > going to take some getting used to... This is true, but only because those other languages don't have blocks. Note that the following calls are equivalent: func("this", "is", "a", "test") { return_value("something") } func "this", "is", "a", "test" do return_value "something" end my_block = proc { return_value("something") } func "this", "is", "a", "test", &my_block my_block = proc { return_value("something") } func("this", "is", "a", "test", &my_block) The third and fourth versions are the call-site equivalent of the `def func(*args, &a_block)` definition I mentioned above. I hope this helps shed a little light on the issue, and gives you some pointers for further research. -- Matthew Kerwin, B.Sc (CompSci) (Hons) http://matthew.kerwin.net.au/ ABN: 59-013-727-651 "You'll never find a programming language that frees you from the burden of clarifying your ideas." - xkcd
on 2013-02-07 08:11
Hi Matthew,
Thanks. That was very helpful.
By the way, when I do: def func(*args,&ablock), I found:
func("this","is","a","test", proc { return_value("something") })
The "proc" ends up as the final member of args, so the yield fails. But
both of the following worked:
my_block = proc { return_value("something") }
func("this","is","a","test",&my_block)
and
func("this","is","a","test", &proc { return_value("something") })
For now I think I'll stick with:
func("this","is","a","test"){ return_value("something") }
It makes some sense to me now and avoids the need for "proc".
Thanks again,
Rob
on 2013-02-07 09:30
Rob Marshall wrote in post #1095677: > Hi Matthew, > > Thanks. That was very helpful. Glad to help. > By the way, when I do: def func(*args,&ablock), I found: > > func("this","is","a","test", proc { return_value("something") }) > > The "proc" ends up as the final member of args, so the yield fails. Indeed, because the 'proc' function returns an object, which isn't special in any way. It's just another object, so becomes just another arg. > But both of the following worked: > > my_block = proc { return_value("something") } > func("this","is","a","test",&my_block) > > and > > func("this","is","a","test", &proc { return_value("something") }) Indeed. In either case the 'proc' function is evaluated before 'func', and 'proc' returns a Proc object. A Proc is basically a wrapper around a block; the & operator instructs ruby to peel back the Proc, revealing the inner block. > For now I think I'll stick with: > > func("this","is","a","test"){ return_value("something") } > > It makes some sense to me now and avoids the need for "proc". It's also a much more sensible solution, and what most normal people would use. ;) But now you're aware that 'proc' is a thing, and one day it might come in handy.
on 2013-02-07 11:21
On Thu, Feb 7, 2013 at 1:39 AM, Rob Marshall <lists@ruby-forum.com> wrote: > Hi Robert, > > At present I'm using the approach I mentioned before, but let me try and > explain my confusion... OK, I'll try to clarify where I can. > for the function as: > > def func(*args) > yield > end > > and not only get the args specified between the parens in *args, but > also still pass the block and "yield" it, is a very new, and foreign, > concept for me. Well, every language has its features which one needs to get used to. :-) A block is special since Ruby will happily pass it to *every* method called even though the method does not do anything with it. And it does not need any declaration. Unless you want to store it somewhere where the &argument_name syntax is needed (see my last post). But even that does not affect errors you are seeing: > this > is > a > test > This is the returning value: something > LocalJumpError: no block given > from (irb):108:in `func' > from (irb):110 > > This one is sort-of obvious since the function return_value [...] > is evaluated prior to being passed as an argument to func. Exactly. Not a function is passed but the result of evaluating a function. Nothing special here. That would be the same as in other languages. And it doesn't really make a difference in what position you have the method call. Same as in other languages. I think you're making it more difficult for you to understand than necessary by introducing #return_value here: this is just an expression as all the literals you have in the invocation above. > But since there's no block, I get the LocalJumpError. Yes, that's caused by invoking yield in absence of a block passed. Btw you can check that with #block_given? irb(main):001:0> def f; block_given? end => nil irb(main):002:0> f => false irb(main):003:0> f {} => true >>> func "this", "is", "a", "test", { return_value("something") } > SyntaxError: compile error > (irb):111: odd number list for Hash > from (irb):111 > > This is is also somewhat obvious because it isn't treating the {} as > defining a block but attempting to define a hash...That's because I > haven't "formally" defined the arguments, so the interpreter assumes > everything is supposed to be an argument and not a block. No, this is wrong. That has absolutely nothing to do with how you defined the method! This is a syntax error. And it has to do with different precedence of {} and do end. You don't even have to define the method for getting those errors - it's all about how the call is written: $ ruby -ce 'func "this", "is", "a", "test", { return_value("something") }' -e:1: syntax error, unexpected '}', expecting tASSOC $ ruby -ce 'func "this", "is", "a", "test" { return_value("something") }' -e:1: syntax error, unexpected '{', expecting $end func "this", "is", "a", "test" { return_value("something") } ^ $ ruby -ce 'func("this", "is", "a", "test") { return_value("something") }' Syntax OK $ ruby -ce 'func "this", "is", "a", "test" do return_value("something") end' Syntax OK For Ruby at the calling location all methods have the same signature *at compile time*. Issues like wrong number of arguments etc. are only detected at runtime! $ ruby -ce 'def f(a) p a end; f(); f(1); f(1,2)' Syntax OK $ ruby -e 'def f(a) p a end; f(); f(1); f(1,2)' -e:1:in `f': wrong number of arguments (0 for 1) (ArgumentError) from -e:1:in `<main>' $ ruby -e 'def f(a) p a end; f(1); f(1,2)' 1 -e:1:in `f': wrong number of arguments (2 for 1) (ArgumentError) from -e:1:in `<main>' So, to put it differently, for Ruby all methods have the same signature f(*args, &block) when it looks at them on calling sites at compile time. >>> func("this", "is", "a", "test") { return_value("something") } > this > is > a > test > => "This is the returning value: something" > > This is still the odd one for me that now (sort-of) makes sense but > would NOT be allowed in any language I've used in the past. So it's > going to take some getting used to... So far your issues have been only syntax errors and they are totally unrelated to how you define the method. I think that's where you took the wrong turn: Ruby is not checking whether argument lists are properly passed or whether a method uses a block (all will _accept_ a block!) at compile time. Only at runtime Ruby will notice - wrong number of arguments - using a block when none was passed Kind regards robert
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.