Method lookup from a Lambda

J

John Conti

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
 
S

Sean O'Halpin

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
 
J

John Conti

Sean said:
It does with the fix below.

# 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
 
S

Sean O'Halpin

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 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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,697
Latest member
AugustNabo

Latest Threads

Top