2010-06-01 85 views

回答

20

这一个是有点棘手追查。查看Proc#lambda? for 1.9的文档,关于proclamdba之间的区别有相当长的讨论。

结论是lambda执行正确数量的参数,并且proc没有。并从该文档,大约只有这样才能转换成PROC的λ在该示例中被示出:

define_method总是定义的方法没有花样,即使非拉姆达PROC对象中给出。这是窍门不被保留的唯一例外。

class C 
    define_method(:e, &proc {}) 
end 
C.new.e(1,2)  => ArgumentError 
C.new.method(:e).to_proc.lambda? => true 

如果你想避免污染任何类,你可以只为了定义一个匿名对象的单身方法强迫一个proclambda

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

谢谢合作!非常有帮助:) define_method最终产生了lambda的事实促成了我的困惑。 – 2010-06-01 12:47:33

+3

有趣的问题时间:你如何在jruby中做到这一点? – Schneems 2012-11-05 19:50:47

+1

回答我的有趣问题:http://stackoverflow.com/questions/13239338/convert-bloc-to-lambda-in-jruby – Schneems 2013-06-10 17:49:33

0

上面的代码与instance_exec不能很好地发挥,但我认为有一个简单的解决方案。在这里,我有这说明了问题和解决方案的例子:

# /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基本上是由马克提出的实施,to_lambda2是“固定”的代码。

从上面的脚本的输出是:

l1.lambda?: true 
l2.lambda?: true 
to_lambda1: Object 
to_lambda2: A 

事实上,我期望instance_exec输出A,不Objectinstance_exec应该改变绑定)。我不知道为什么它的工作方式不同,但我想define_singleton_method返回尚未绑定到Object的方法,并且Object#method返回已绑定的方法。

4

它是不是可能将proc转换为lambda没有麻烦。 Mark Rushakoff的答案并不保留块中self的值,因为self变成了Object.new。 Pawel Tomulik的答案不能用于Ruby 2.1,因为define_singleton_method现在返回符号,所以to_lambda2返回:_.to_proc

我的回答是也是错误

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 

它保留的self块中的价值:

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 

但它失败instance_exec

puts 66.instance_exec &p # 66 
puts 66.instance_exec &q # 42, should be 66 

我必须使用block.binding.eval('self')找到正确的对象。我把我的方法放在一个匿名模块中,所以它不会污染任何类。然后我将我的方法绑定到正确的对象上。尽管该对象从未包含该模块,但它仍然有效!绑定方法产生一个lambda。

66.instance_exec &q失败,因为q是秘密绑定到42的方法,并且instance_exec不能重新绑定该方法。可以通过扩展q来公开未绑定的方法,并重新定义instance_exec以将未绑定的方法绑定到不同的对象。即使如此,module_execclass_exec仍然会失败。

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) 

的问题是,Hash.class_exec &$q定义Array#greet和不Hash#greet。 (虽然$q是秘密的匿名模块的一种方法,它仍然在Array中定义方法,而不是在匿名模块中)。对于原始proc,Hash.class_exec &$p将定义Hash#greet。我得出结论convert_to_lambda是错误的,因为它不适用于class_exec

3

以下是可能的解决方案:

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" 

应该红宝石2.1+