Можно ли преобразовать Proc-ароматизированный Proc в Lambda-ароматизированный Proc?
Бит удивил, что это не работает, по крайней мере, в 1.9.2:
my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
Можно ли преобразовать Proc-ароматизированный Proc в Lambda-ароматизированный Proc?
Бит удивил, что это не работает, по крайней мере, в 1.9.2:
my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
Это было немного сложно найти. Глядя на документы для Proc#lambda?
для 1.9, существует довольно продолжительное обсуждение разницы между proc
и lamdba
s.
Что происходит, так это то, что 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
Если вы хотите избежать загрязнения любого класса, вы можете просто определить метод singleton для анонимного объекта, чтобы принудить 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 в лямбда без проблем. Ответ Марка Рушакова не сохраняет значение 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')
для поиска правильного объекта. Я помещаю свой метод в анонимный модуль, поэтому он никогда не загрязняет какой-либо класс. Затем я привязываю свой метод к правильному объекту. Это работает, хотя объект никогда не включал модуль! Связанный метод делает лямбда.
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
, а не в анонимном модуле.) С исходным 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: 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
является "фиксированным" кодом.
Выход выше script:
l1.lambda?: true
l2.lambda?: true
to_lambda1: Object
to_lambda2: A
На самом деле я ожидал бы instance_exec
для вывода A
, а не Object
(instance_exec
должен изменить привязку). Я не знаю, почему это работает по-другому, но я полагаю, что define_singleton_method
возвращает метод, который еще не привязан к Object
, а Object#method
возвращает уже связанный метод.