Ruby: конвертировать proc в лямбду? - PullRequest
14 голосов
/ 01 июня 2010

Можно ли преобразовать Proc-ароматизированный Proc в лямбда-ароматизированный Proc?

Немного удивлен, что это не работает, по крайней мере, в 1.9.2:

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

Ответы [ 5 ]

20 голосов
/ 01 июня 2010

Это было немного сложно найти. Глядя на документы для Proc#lambda? для 1,9 , довольно долго обсуждается разница между proc с и lamdba с.

То, что сводится к тому, что lambda обеспечивает правильное количество аргументов, а proc - нет. И из этой документации о единственном способе преобразования proc в лямбду показано в этом примере:

define_method всегда определяет метод без уловок, даже если задан не лямбда-объект 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
5 голосов
/ 08 мая 2014

не можно без проблем конвертировать процесс в лямбду. Ответ Марка Рушакова не сохраняет значение self в блоке, потому что self становится Object.new. Ответ Павла Томулика не может работать с 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'), чтобы найти правильный объект. Я поместил свой метод в анонимный модуль, чтобы он никогда не загрязнял ни один класс. Затем я привязываю свой метод к правильному объекту. Это работает, хотя объект никогда не включал модуль! Связанный метод создает лямбду.

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)

Проблема в том, что Hash.class_exec &$q определяет Array#greet, а не Hash#greet. (Хотя $q является тайным методом анонимного модуля, он по-прежнему определяет методы в Array, а не в анонимном модуле.) В исходном протоколе Hash.class_exec &$p будет определять Hash#greet. Я пришел к выводу, что convert_to_lambda неверно, потому что он не работает с class_exec.

4 голосов
/ 26 июля 2014

Вот возможное решение:

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 +

2 голосов
/ 12 июля 2013

Кросс-ruby-совместимая библиотека для преобразования проков в лямбды: https://github.com/schneems/proc_to_lambda

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

0 голосов
/ 28 января 2014

Приведенный выше код не очень хорошо работает с 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 - это в основном реализация, предложенная Марком, to_lambda2 - это «фиксированный» код.

Вывод сценария выше:

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

На самом деле я бы ожидал, что instance_exec выдаст A, а не Object (instance_exec должен изменить привязку). Я не знаю, почему это работает иначе, но я предполагаю, что define_singleton_method возвращает метод, который еще не связан с Object, а Object#method возвращает уже связанный метод.

...