Ruby:把proc转换成lambda?

Ruby: convert proc to lambda?

是否可以将一个proc-flavered proc转换为lambda-flavered proc?

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

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

这个有点难找到。看一下1.9版的Proc#lambda?文档,我们对proclamdba之间的区别进行了相当长的讨论。

归根结底,lambda强制执行正确数量的参数,而proc则不强制执行。从该文档中可以看出,将proc转换为lambda的唯一方法如下:

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.

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

如果要避免污染任何类,只需在匿名对象上定义一个singleton方法,以便将proc强制为lambda

1
2
3
4
5
6
7
8
9
10
11
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


不可能毫无困难地将过程转换为lambda。Mark Rushakoff的回答没有保留该区块中self的价值,因为self变成了Object.new。pawel-tomulik的答案不能与ruby 2.1一起使用,因为define_singleton_method现在返回一个符号,所以to_lambda2返回:_.to_proc

我的回答也是错误的:

1
2
3
4
5
6
7
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的价值:

1
2
3
4
5
6
7
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中失败了:

1
2
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仍将失败。

1
2
3
4
5
6
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


以下是可能的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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的Cross Ruby兼容库:https://github.com/schneems/proc_到_lambda

杰姆:http://rubygems.org/gems/proc_to_lambda网站


上面的代码不能很好地与instance_exec配合使用,但我认为有简单的修复方法。这里我有一个例子,说明了这个问题和解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# /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}
"

end
print"l1.lambda?: #{l1.lambda?}
"


l2 = to_lambda2 do
  print"to_lambda2: #{self.class.name}
"

end
print"l2.lambda?: #{l2.lambda?}
"


class A; end

A.new.instance_exec &l1
A.new.instance_exec &l2

to_lambda1基本上是由mark提出的实现,to_lambda2是一个"固定"的代码。

上面脚本的输出是:

1
2
3
4
l1.lambda?: true
l2.lambda?: true
to_lambda1: Object
to_lambda2: A

实际上,我希望instance_exec输出A,而不是Object(instance_exec应该改变绑定)。我不知道为什么这会有不同的效果,但是我假设define_singleton_method返回一个还没有绑定到Object的方法,Object#method返回一个已经绑定的方法。