[#3986] Re: Principle of least effort -- another Ruby virtue. — Andrew Hunt <andy@...>

> Principle of Least Effort.

14 messages 2000/07/14

[#4043] What are you using Ruby for? — Dave Thomas <Dave@...>

16 messages 2000/07/16

[#4139] Facilitating Ruby self-propagation with the rig-it autopolymorph application. — Conrad Schneiker <schneik@...>

Hi,

11 messages 2000/07/20

[ruby-talk:03949] Pluggable functions and blocks

From: Aleksi Niemel<aleksi.niemela@...>
Date: 2000-07-12 13:06:41 UTC
List: ruby-talk #3949
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

In This Thread

Prev Next