Is it possible to convert a proc-flavored Proc into a lambda-flavored Proc?
Bit surprised that this doesn't work, at least in 1.9.2:
my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
This one was a bit tricky to track down. Looking at the docs for Proc#lambda?
for 1.9, there's a fairly lengthy discussion about the difference between proc
s and lamdba
s.
What it comes down to is that a lambda
enforces the correct number of arguments, and a proc
doesn't. And from that documentation, about the only way to convert a proc into a lambda is shown in this example:
define_method
always defines a method without the tricks, even if a non-lambda Proc object is given. This is the only exception which the tricks are not preserved.class C define_method(:e, &proc {}) end C.new.e(1,2) => ArgumentError C.new.method(:e).to_proc.lambda? => true
If you want to avoid polluting any class, you can just define a singleton method on an anonymous object in order to coerce a proc
to a lambda
:
def convert_to_lambda &block
obj = Object.new
obj.define_singleton_method(:_, &block)
return obj.method(:_).to_proc
end
p = Proc.new {}
puts p.lambda? # false
puts(convert_to_lambda(&p).lambda?) # true
puts(convert_to_lambda(&(lambda {})).lambda?) # true
It is not possible to convert a proc to a lambda without trouble. The answer by Mark Rushakoff doesn't preserve the value of self
in the block, because self
becomes Object.new
. The answer by Pawel Tomulik can't work with Ruby 2.1, because define_singleton_method
now returns a Symbol, so to_lambda2
returns :_.to_proc
.
My answer is also wrong:
def convert_to_lambda &block
obj = block.binding.eval('self')
Module.new.module_exec do
define_method(:_, &block)
instance_method(:_).bind(obj).to_proc
end
end
It preserves the value of self
in the block:
p = 42.instance_exec { proc { self }}
puts p.lambda? # false
puts p.call # 42
q = convert_to_lambda &p
puts q.lambda? # true
puts q.call # 42
But it fails with instance_exec
:
puts 66.instance_exec &p # 66
puts 66.instance_exec &q # 42, should be 66
I must use block.binding.eval('self')
to find the correct object. I put my method in an anonymous module, so it never pollutes any class. Then I bind my method to the correct object. This works though the object never included the module! The bound method makes a lambda.
66.instance_exec &q
fails because q
is secretly a method bound to 42
, and instance_exec
can't rebind the method. One might fix this by extending q
to expose the unbound method, and redefining instance_exec
to bind the unbound method to a different object. Even so, module_exec
and class_exec
would still fail.
class Array
$p = proc { def greet; puts "Hi!"; end }
end
$q = convert_to_lambda &$p
Hash.class_exec &$q
{}.greet # undefined method `greet' for {}:Hash (NoMethodError)
The problem is that Hash.class_exec &$q
defines Array#greet
and not Hash#greet
. (Though $q
is secretly a method of an anonymous module, it still defines methods in Array
, not in the anonymous module.) With the original proc, Hash.class_exec &$p
would define Hash#greet
. I conclude that convert_to_lambda
is wrong because it doesn't work with class_exec
.
Here is possible solution:
class Proc
def to_lambda
return self if lambda?
# Save local reference to self so we can use it in module_exec/lambda scopes
source_proc = self
# Convert proc to unbound method
unbound_method = Module.new.module_exec do
instance_method( define_method( :_proc_call, &source_proc ))
end
# Return lambda which binds our unbound method to correct receiver and calls it with given args/block
lambda do |*args, &block|
# If binding doesn't changed (eg. lambda_obj.call) then bind method to original proc binding,
# otherwise bind to current binding (eg. instance_exec(&lambda_obj)).
unbound_method.bind( self == source_proc ? source_proc.receiver : self ).call( *args, &block )
end
end
def receiver
binding.eval( "self" )
end
end
p1 = Proc.new { puts "self = #{self.inspect}" }
l1 = p1.to_lambda
p1.call #=> self = main
l1.call #=> self = main
p1.call( 42 ) #=> self = main
l1.call( 42 ) #=> ArgumentError: wrong number of arguments (1 for 0)
42.instance_exec( &p1 ) #=> self = 42
42.instance_exec( &l1 ) #=> self = 42
p2 = Proc.new { return "foo" }
l2 = p2.to_lambda
p2.call #=> LocalJumpError: unexpected return
l2.call #=> "foo"
Should work on Ruby 2.1+
Cross ruby compatiable library for converting procs to lambdas: https://github.com/schneems/proc_to_lambda
The above code doesn't play nicely with instance_exec
but I think there is simple fix for that. Here I have an example which illustrates the issue and solution:
# /tmp/test.rb
def to_lambda1(&block)
obj = Object.new
obj.define_singleton_method(:_,&block)
obj.method(:_).to_proc
end
def to_lambda2(&block)
Object.new.define_singleton_method(:_,&block).to_proc
end
l1 = to_lambda1 do
print "to_lambda1: #{self.class.name}\n"
end
print "l1.lambda?: #{l1.lambda?}\n"
l2 = to_lambda2 do
print "to_lambda2: #{self.class.name}\n"
end
print "l2.lambda?: #{l2.lambda?}\n"
class A; end
A.new.instance_exec &l1
A.new.instance_exec &l2
to_lambda1
is basically the implementation proposed by Mark, to_lambda2
is a "fixed" code.
The output from above script is:
l1.lambda?: true
l2.lambda?: true
to_lambda1: Object
to_lambda2: A
In fact I'd expect instance_exec
to output A
, not Object
(instance_exec
should change binding). I don't know why this work differently, but I suppose that define_singleton_method
returns a method that is not yet bound to Object
and Object#method
returns an already bound method.
来源:https://stackoverflow.com/questions/2946603/ruby-convert-proc-to-lambda