Ruby:将proc转换为lambda?
Posted
技术标签:
【中文标题】Ruby:将proc转换为lambda?【英文标题】:Ruby: convert proc to lambda? 【发布时间】:2011-02-26 03:48:03 【问题描述】:是否可以将 proc 风格的 Proc 转换为 lambda 风格的 Proc?
有点惊讶这不起作用,至少在 1.9.2 中:
my_proc = proc |x| x
my_lambda = lambda &p
my_lambda.lambda? # => false!
【问题讨论】:
【参考方案1】:这个有点难以追踪。查看Proc#lambda?
for 1.9 的文档,关于proc
s 和lamdba
s 之间的区别进行了相当长的讨论。
归结为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
【讨论】:
谢谢!非常有帮助 :) define_method 最终产生了一个 lambda 的事实让我感到困惑。 有趣的提问时间:你如何在 jruby 中做到这一点? 回答我有趣的问题:***.com/questions/13239338/… 如果你有兴趣让它成为一个稳定的界面。您能否就这个问题发表评论,说明您为什么首先要将 proc 转换为 lambda? bugs.ruby-lang.org/issues/9777#change-46353【参考方案2】:不可能轻松地将 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_exec
和 class_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
中定义方法,而不是在匿名模块中。)使用原始过程,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"
应该在 Ruby 2.1+ 上工作
【讨论】:
【参考方案4】:用于将 procs 转换为 lambdas 的跨 ruby 兼容库: https://github.com/schneems/proc_to_lambda
宝石: http://rubygems.org/gems/proc_to_lambda
【讨论】:
【参考方案5】:上面的代码不能很好地与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
,而不是Object
(instance_exec
应该更改绑定)。我不知道为什么它的工作方式不同,但我想define_singleton_method
返回一个尚未绑定到Object
的方法,而Object#method
返回一个已经绑定的方法。
【讨论】:
以上是关于Ruby:将proc转换为lambda?的主要内容,如果未能解决你的问题,请参考以下文章