簡體   English   中英

Ruby:將proc轉換為lambda?

[英]Ruby: convert proc to lambda?

是否有可能將proc風味的Proc轉換為lambda風味的Proc?

有點驚訝,這不起作用,至少在1.9.2:

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

追蹤這個有點棘手。 查看Proc#lambda?的文檔Proc#lambda? 對於1.9 ,關於proclamdba之間的區別有一個相當冗長的討論。

它歸結為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

這是不可能的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

它保留了塊中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

這是可能的解決方案:

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的跨ruby兼容庫: https//github.com/schneems/proc_to_lambda

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

上面的代碼與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返回一個已經綁定的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM