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 methodfoo?’ 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

On Fri, May 2, 2008 at 6:35 AM, John C. [email protected] wrote:

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.

  lambda do

use instance_eval(&block) instead of block.call

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

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

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

On Fri, May 9, 2008 at 11:01 PM, John C. [email protected] 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?

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

instance_exec for ruby 1.8 by Mauricio F.

eigenclass.org

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