Ruby:将proc转换为lambda?

是否有可能将proc-flavored Proc转换为lambda风味的Proc?

有点惊讶,这不起作用,至少在1.9.2:

my_proc = proc {|x| x} my_lambda = lambda &p my_lambda.lambda? # => false! 

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

它归结为lambda强制执行正确数量的参数,而proc则不执行。 从该文档中,关于将proc转换为lambda的唯一方法如下所示:

define_method总是定义一个没有技巧的方法,即使给出了非lambda Proc对象。 这是唯一不保留技巧的例外。

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

如果你想避免污染任何类,你可以在匿名对象上定义一个单例方法,以便将一个proc强制转换为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 

无法将proc转换为lambda。 Mark Rushakoff的答案并没有保留self在块中的价值,因为self变成了Object.new 。 Pawel Tomulik的答案不适用于Ruby 2.1,因为define_singleton_method现在返回一个Symbol,所以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

这是可能的解决方案:

 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" 

应该在Ruby 2.1+上工作

用于将procs转换为lambdas的跨ruby兼容库: https : //github.com/schneems/proc_to_lambda

gem: http : //rubygems.org/gems/proc_to_lambda

上面的代码与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基本上是Mark提出的实现, 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返回一个已经绑定的方法。