From: "Aleksi Niemelä" Date: 2000-07-12T15:06:41+02:00 Subject: [ruby-talk:03949] Pluggable functions and blocks Quite longish, but try to bear with me. There's a lot of questions too. They are just hidden inside the mass of text :). This question might too easy, but it's not apparently to me. I try to make it as easy as possible for user to use pluggable functions (which means to code without eval in any form, without class or module wrappers etc.). I'd like to have these little snippets of code in other 'configuration' files, but first I try to use pluggable functions in one file. I'm not very picky about naming convention here. I talk about function instead of method, even while all the code are really in methods or in blocks, because the nature of the problem is just to have piece of code and use it elsewhere. There should be nothing (visible) extra. So here goes my simple example: class Foo def Foo.foo puts "foo" end def do_foo( foo = proc {Foo.foo} ) foo.call end end def my_foo puts "bar" end Foo.new.do_foo Foo.new.do_foo( proc {my_foo} ) # I'd like to say do_foo( my_foo ) This seems to work, outputting 'foo\nbar\n' as expected. It's tedious anyway, and tediousness will turn into error-prone code later. The main problem is to have explicit proc wrappings over functions. When the logic is a little bit complex we will hit the first problem: class Foo def initialize(bool=true) @bool = bool end def Foo.foo(bool) if bool return "foo" else return "no foo this time" end end def do_foo( foo = proc {Foo.foo} ) puts foo.call(@bool) end end def my_foo(bool) if bool return "bar" else return "no bar either" end end Foo.new.do_foo Foo.new(false).do_foo Foo.new.do_foo( proc {my_foo} ) Foo.new(false).do_foo( proc {my_foo} ) This won't work anymore. The reason is that when we add an argument to pluggable function we have to change each proc-wrappings. So then I came up with a "clever" solution which yet another time post-pones the real fix of the problem. class Foo def initialize(bool=true) @bool = bool end def Foo.foo(bool) if bool return "foo" else return "no foo this time" end end def do_foo( foo = proc {|*args| Foo.foo(*args)} ) puts foo.call(@bool) end end def my_foo(bool) if bool return "bar" else return "no bar either" end end Foo.new.do_foo Foo.new(false).do_foo Foo.new.do_foo( proc {|*args| my_foo(*args)} ) Foo.new(false).do_foo( proc {|*args| my_foo(*args)} ) Here just I'm probably stupid, but I can't make this work. This outputs: foo pluggable_func_4.rb:12:in `foo': wrong # of arguments(0 for 1) (ArgumentError) from pluggable_func_4.rb:12:in `do_foo' from pluggable_func_4.rb:26:in `call' from pluggable_func_4.rb:13:in `do_foo' from pluggable_func_4.rb:26 Anyway if I'd be able to continue my story I'd point out few points: Wrapping my_foo immediately to proc object by saying my_foo = proc do |bool| if bool return "bar" else return "no bar either" end end leads to: pluggable_func_5.rb:19: return appeared outside of method pluggable_func_5.rb:21: return appeared outside of method and making a factory: class Foo def initialize(bool=true) @bool = bool end def Foo.foo(bool) if bool return "foo" else return "no foo this time" end end def do_foo( foo = proc {|bool| Foo.foo(bool)} ) puts foo.call(@bool) end end def my_foo return proc { |bool| if bool return "bar" else return "no bar either" end } end Foo.new.do_foo Foo.new(false).do_foo Foo.new.do_foo( my_foo ) Foo.new(false).do_foo( my_foo ) leads to: foo no foo this time pluggable_func_6.rb:21:in `call': return from proc-closure (LocalJumpError) from pluggable_func_6.rb:13:in `do_foo' from pluggable_func_6.rb:31 because you can't return from block from multiple locations. The return value is the evaluated value of the last expression. In a nut shell: ** Turning 'def'-style code into proc leads to partial rewrite; you can't return as freely as in 'def' ** Turning 'def'-style code into 'proc' may lead to runtime errors instead of compile time errors This means that proc and definitions are inherently different (more than additional binding to variables). I wonder why this is so. Or maybe I should rephrase and ask something different. Why there is no hierarcy for methods (as a example of the interface, by no means the real thing) class Method # abstract def call end end class ClassMethod < Method @klass, @method def initialize( klass, method ) end end class InstanceMethod < Method @instance, @method def initialize( instance, method ) end end class Block < Method @block_binding def initialize( proc = nil, &block) end end then one could say def use_pluggable_function( func ) puts func.call() end and don't care whether we happen to have a class method, a method for particular instance (so the real call will be 'instance.method_name(args)'), or a block. I'm sure there's a good reason for this. Most likely one just don't need them, you're able to code without such helper objects, or they're implemented already in different (or same) form. Finally, this all has lead me to think why we have blocks in the first place. I try to handle them as a special case of syntax to write code neatly (like 'for' is special case for 'each'; actually a little bit more special than just syntax). In reality they turn out to be more than a subroutine associated with external variable binding. How or for what, I don't know. I don't know what's so special with them. I like the idea of a method associated with a block, used powerfully in standard library, but don't know why it ends there. If I think the associated block as a optional argument (which it is, to quite big extent) I don't know why we can't associate more blocks. I don't know how often this is useful, but example cases might be array.sort do primary_criteria end, do secondary_criteria end or set_callbacks_for_gui_object enter_cb, action_cb, leave_cb Maybe I'm seeing things in too Perlish way but I think the way it handles closures (or callback or "blocks") was easy. Writing "set_callbacks( &callback, $cb_ref );" feels quite natural to me. It's time for me to use more Ruby, so I appreciate if some one enlightens me. - Aleksi