【问题标题】:Ruby: convert proc to lambda?Ruby:将proc转换为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!

【问题讨论】:

    标签: ruby lambda proc-object


    【解决方案1】:

    这个有点难以追踪。查看Proc#lambda? for 1.9 的文档,关于procs 和lamdbas 之间的区别进行了相当长的讨论。

    归结为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 中做到这一点?
    • 回答我有趣的问题:stackoverflow.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_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 中定义方法,而不是在匿名模块中。)使用原始过程,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,而不是Objectinstance_exec 应该更改绑定)。我不知道为什么它的工作方式不同,但我想define_singleton_method 返回一个尚未绑定到Object 的方法,而Object#method 返回一个已经绑定的方法。

          【讨论】:

            猜你喜欢
            • 2013-04-18
            • 2021-06-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-12
            • 1970-01-01
            • 1970-01-01
            • 2010-09-06
            相关资源
            最近更新 更多