Method lookup from a Lambda

Hi all,

I am not sure if my question is relatively obvious or instead arcane.
Being new to ruby, I am trying out some of the metaprogramming features
to generate sets of orthogonal methods for decoding various string
types. I want to provide these methods as mix-ins so many of my classes
can decode these specialized strings. The methods have lots of
repetitive code and are easily reduced using lambdas (think DRY).

However, all has not gone as I expected. This works:

  module Fix
    def self.manage()
      lambda do
        what = foo? ? "fix" : "leave"
        "Let's " + what + " it"
      end
    end

    module_eval %{define_method :maintenance, manage}
  end

  class X
    include Fix
    def foo?; true; end
  end

  class Y
    include Fix
    def foo?; false; end
  end

  puts X.new.maintenance
  puts Y.new.maintenance

  # Let's fix it
  # Let's leave it

This does not:

  module Fix
    def self.doer()
      lambda do
        foo? ? "fix" : "leave"
      end
    end

    def self.manage()
      grunt = doer
      lambda do
        "Let's " + grunt.call + " it"
      end
    end

    module_eval %{define_method :maintenance, manage}
  end

  class X
    include Fix
    def foo?; true; end
  end

  class Y
    include Fix
    def foo?; false; end
  end

  puts X.new.maintenance
  puts Y.new.maintenance

  # lambda2.rb:4:in `doer': undefined method `foo?' for Fix:Module
(NoMethodError)
  # from lambda2.rb:10:in `call'
  # from lambda2.rb:10:in `maintenance'
  # from lambda2.rb:25

In the first example, the call to the method the mixin module relies on
gets called without problem. I. e. the instance method of the mixin
finds the object's instance method. However, in the second example,
methods are not looked up the same way, the module is searched and so it
fails.

What about the second example's closure on the lambda is causing
method's to be looked up differently? I do not feel like I have a good
mental model of the method lookup and scoping rules of Ruby. The latest
book I've looked at (Programming Ruby) doesn't seem to shed much light
on my issue.

Can anyone give me an explanation to help?

Thank you,
John

···

--
Posted via http://www.ruby-forum.com/.

Hi all,

Hi

I am not sure if my question is relatively obvious or instead arcane.
Being new to ruby, I am trying out some of the metaprogramming features
to generate sets of orthogonal methods for decoding various string
types. I want to provide these methods as mix-ins so many of my classes
can decode these specialized strings. The methods have lots of
repetitive code and are easily reduced using lambdas (think DRY).

However, all has not gone as I expected. This works:

[snip code]

This does not:

It does with the fix below.

  module Fix
    def self.doer()
      lambda do
        foo? ? "fix" : "leave"
      end
    end

    def self.manage()
      grunt = doer
      lambda do

# use instance_eval(&block) instead of block.call

         "Let's " + instance_eval(&grunt) + " it"

      end
    end

    module_eval %{define_method :maintenance, manage}
  end

  class X
    include Fix
    def foo?; true; end
  end

  class Y
    include Fix
    def foo?; false; end
  end

  puts X.new.maintenance
  puts Y.new.maintenance

Output:
Let's fix it
Let's leave it

Can anyone give me an explanation to help?

When you use block.call, the block is evaluated in the context in
which it was defined. In the case of the block defined in #doer this
is the singleton class of the module Fix, which does not have a #foo?
method. Using instance_eval tells Ruby to evaluate the block in the
context of an object instance. Without a specific receiver, this is
the current #self - in this case, the object of the class in which you
have included the module Fix. This does have a #foo? method, so the
block executes.

HTH

Regards,
Sean

···

On Fri, May 2, 2008 at 6:35 AM, John Conti <john@john-conti.com> wrote:

Sean O'halpin wrote:

It does with the fix below.

      lambda do

# use instance_eval(&block) instead of block.call

         "Let's " + instance_eval(&grunt) + " it"

Is there a way to pass arguments to &grunt during the invocation? The
call method allows arguments to be passed to the lambda's that expect
them. While my example does not show such a lambda, this is a feature I
currently require. If grunt did require arguments, how could I pass
them?

TIA,
John

···

--
Posted via http://www.ruby-forum.com/\.

Hi,

You need instance_exec, which is native in 1.9 but has to be
implemented in Ruby in 1.8.
The version below is the best one I have to hand. (There may be a
better one on Mauricio's blog but eigenclass.org is coming up with a
hiki error at the moment so I can't check).

Regards,
Sean

···

On Fri, May 9, 2008 at 11:01 PM, John Conti <john@john-conti.com> wrote:

         "Let's " + instance_eval(&grunt) + " it"

Is there a way to pass arguments to &grunt during the invocation? The
call method allows arguments to be passed to the lambda's that expect
them. While my example does not show such a lambda, this is a feature I
currently require. If grunt did require arguments, how could I pass
them?

#
# instance_exec for ruby 1.8 by Mauricio Fernandez
# http://eigenclass.org/hiki.rb?bounded+space+instance_exec
# thread-safe and handles frozen objects in bounded space
#
class Object
  module InstanceExecHelper; end
  include InstanceExecHelper
  def instance_exec(*args, &block)
    begin
      old_critical, Thread.critical = Thread.critical, true
      n = 0
      methods = InstanceExecHelper.instance_methods
      # this in order to make the lookup O(1), and name generation O(n) on the
      # number of nested/concurrent instance_exec calls instead of O(n**2)
      table = Hash[*methods.zip(methods).flatten]
      n += 1 while table.has_key?(mname="__instance_exec#{n}")
    ensure
      Thread.critical = old_critical
    end
    InstanceExecHelper.module_eval{ define_method(mname, &block) }
    begin
      ret = send(mname, *args)
    ensure
      InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
    end
    ret
  end
end