Ruby:将 proc 转换为 lambda?

发布于 2024-09-04 09:14:13 字数 189 浏览 5 评论 0原文

是否可以将 proc 风格的 Proc 转换为 lambda 风格的 Proc?

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

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

Is it possible to convert a proc-flavored Proc into a lambda-flavored Proc?

Bit surprised that this doesn't work, at least in 1.9.2:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

偏闹i 2024-09-11 09:14:13

追踪这个有点棘手。查看 Proc#lambda? 对于 1.9,有一个关于 proc 和 lamdba 之间差异的相当长的讨论。

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

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

<前><代码>C类
Define_method(:e, &proc {})
结尾
C.new.e(1,2) =>参数错误
C.new.method(:e).to_proc.lambda? =>真的

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

This one was a bit tricky to track down. Looking at the docs for Proc#lambda? for 1.9, there's a fairly lengthy discussion about the difference between procs and lamdbas.

What it comes down to is that a lambda enforces the correct number of arguments, and a proc doesn't. And from that documentation, about the only way to convert a proc into a lambda is shown in this example:

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.

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

If you want to avoid polluting any class, you can just define a singleton method on an anonymous object in order to coerce a proc to a 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
花开浅夏 2024-09-11 09:14:13

不可能毫无困难地将 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 一起使用。

It is not possible to convert a proc to a lambda without trouble. The answer by Mark Rushakoff doesn't preserve the value of self in the block, because self becomes Object.new. The answer by Pawel Tomulik can't work with Ruby 2.1, because define_singleton_method now returns a Symbol, so to_lambda2 returns :_.to_proc.

My answer is also wrong:

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

It preserves the value of self in the block:

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

But it fails with instance_exec:

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

I must use block.binding.eval('self') to find the correct object. I put my method in an anonymous module, so it never pollutes any class. Then I bind my method to the correct object. This works though the object never included the module! The bound method makes a lambda.

66.instance_exec &q fails because q is secretly a method bound to 42, and instance_exec can't rebind the method. One might fix this by extending q to expose the unbound method, and redefining instance_exec to bind the unbound method to a different object. Even so, module_exec and class_exec would still fail.

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)

The problem is that Hash.class_exec &$q defines Array#greet and not Hash#greet. (Though $q is secretly a method of an anonymous module, it still defines methods in Array, not in the anonymous module.) With the original proc, Hash.class_exec &$p would define Hash#greet. I conclude that convert_to_lambda is wrong because it doesn't work with class_exec.

茶色山野 2024-09-11 09:14:13

这是可能的解决方案:

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+ 上工作

Here is possible solution:

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"

Should work on Ruby 2.1+

节枝 2024-09-11 09:14:13

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

宝石:
http://rubygems.org/gems/proc_to_lambda

Cross ruby compatiable library for converting procs to lambdas:
https://github.com/schneems/proc_to_lambda

The Gem:
http://rubygems.org/gems/proc_to_lambda

薄暮涼年 2024-09-11 09:14:13

上面的代码不能很好地与 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 返回一个尚未绑定到 ObjectObject#method 的方法返回一个已经绑定的方法。

The above code doesn't play nicely with instance_exec but I think there is simple fix for that. Here I have an example which illustrates the issue and solution:

# /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 is basically the implementation proposed by Mark, to_lambda2 is a "fixed" code.

The output from above script is:

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

In fact I'd expect instance_exec to output A, not Object (instance_exec should change binding). I don't know why this work differently, but I suppose that define_singleton_method returns a method that is not yet bound to Object and Object#method returns an already bound method.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文