Ruby:将proc转换为lambda?

Ala*_*ell 14 ruby lambda proc-object

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

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

my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
Run Code Online (Sandbox Code Playgroud)

Mar*_*off 20

追踪这个有点棘手.Proc#lambda?查看1.9的文档,proc s和lamdbas 之间的区别进行了相当冗长的讨论.

它归结为a lambda强制执行正确数量的参数,而a 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
Run Code Online (Sandbox Code Playgroud)

如果你想避免污染任何类,你可以在匿名对象上定义一个单例方法,以强制a 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
Run Code Online (Sandbox Code Playgroud)

  • 有趣的问题时间:你如何在jruby中做到这一点? (3认同)

Geo*_*ler 5

这是不是可以将PROC转换为拉姆达没有麻烦.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
Run Code Online (Sandbox Code Playgroud)

它保留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
Run Code Online (Sandbox Code Playgroud)

但它失败了instance_exec:

puts 66.instance_exec &p    # 66
puts 66.instance_exec &q    # 42, should be 66
Run Code Online (Sandbox Code Playgroud)

我必须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)
Run Code Online (Sandbox Code Playgroud)

问题是Hash.class_exec &$q定义Array#greet而不是Hash#greet.(虽然$q秘密地是一个匿名模块的方法,但它仍然定义了方法Array,而不是匿名模块.)使用原始proc,Hash.class_exec &$p将定义Hash#greet.我的结论convert_to_lambda是错误的,因为它无效class_exec.


Den*_*kov 5

这是可能的解决方案:

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"
Run Code Online (Sandbox Code Playgroud)

应该适用于 Ruby 2.1+