Est-il possible de convertir un proc Proc aromatisé en un Proc aromatisé lambda?Ruby: convertir proc en lambda?
peu surpris que cela ne fonctionne pas, au moins en 1.9.2:
my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
Est-il possible de convertir un proc Proc aromatisé en un Proc aromatisé lambda?Ruby: convertir proc en lambda?
peu surpris que cela ne fonctionne pas, au moins en 1.9.2:
my_proc = proc {|x| x}
my_lambda = lambda &p
my_lambda.lambda? # => false!
Celui-ci était un peu difficile à trouver. En regardant les documents pour Proc#lambda?
for 1.9, il y a une discussion assez longue sur la différence entre proc
s et lamdba
s. Ce qui revient à dire que lambda
applique le nombre correct d'arguments, ce qui n'est pas le cas pour proc
. Et à partir de cette documentation, la seule façon de convertir un proc dans un lambda est montré dans cet exemple:
define_method
définit toujours une méthode sans les tours, même si un objet Proc non-lambda est donnée. C'est la seule exception que les astuces ne sont pas conservées.class C define_method(:e, &proc {}) end C.new.e(1,2) => ArgumentError C.new.method(:e).to_proc.lambda? => true
Si vous voulez éviter de polluer toute la classe, vous pouvez juste définir une méthode singleton sur un objet anonyme afin de contraindre un proc
à un 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
rubis Croix bibliothèque compatiable pour convertir procs à lambdas: https://github.com/schneems/proc_to_lambda
Le code ci-dessus ne joue pas bien avec instance_exec
mais je pense qu'il y a une solution simple pour cela. Ici, j'ai un exemple qui illustre le problème et la 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
est essentiellement la mise en œuvre proposée par Mark, to_lambda2
est un code « fixe ».
La sortie du script ci-dessus est:
l1.lambda?: true
l2.lambda?: true
to_lambda1: Object
to_lambda2: A
En fait, je me attends instance_exec
à la sortie A
, non Object
(instance_exec
devrait changer la liaison). Je ne sais pas pourquoi cela fonctionne différemment, mais je suppose que define_singleton_method
renvoie une méthode qui n'est pas encore liée à Object
et Object#method
renvoie une méthode déjà liée.
Il est pas possible de convertir un proc en lambda sans problème. La réponse de Mark Rushakoff ne conserve pas la valeur de self
dans le bloc, car self
devient Object.new
. La réponse de Pawel Tomulik ne peut pas fonctionner avec Ruby 2.1, car define_singleton_method
renvoie maintenant un symbole, donc to_lambda2
renvoie :_.to_proc
.
Ma réponse est aussi mal:
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
Il conserve la valeur de self
dans le bloc:
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
Mais il échoue avec instance_exec
:
puts 66.instance_exec &p # 66
puts 66.instance_exec &q # 42, should be 66
I doit utiliser block.binding.eval('self')
pour trouver l'objet correct. Je mets ma méthode dans un module anonyme, donc il ne pollue jamais aucune classe. Ensuite, je lie ma méthode à l'objet correct. Cela fonctionne bien que l'objet n'a jamais inclus le module! La méthode liée fait un lambda.
66.instance_exec &q
échoue parce que q
est secrètement une méthode liée à 42
et instance_exec
ne peut pas lier de nouveau la méthode. Vous pouvez résoudre ce problème en développant q
pour exposer la méthode non liée et en redéfinissant instance_exec
pour lier la méthode non liée à un objet différent. Même ainsi, module_exec
et class_exec
échouerait toujours.
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)
Le problème est que Hash.class_exec &$q
définit Array#greet
et non Hash#greet
. (Bien que $q
est secrètement une méthode d'un module anonyme, il définit toujours des méthodes dans Array
, pas dans le module anonyme.) Avec le proc d'origine, Hash.class_exec &$p
définirait Hash#greet
. Je conclus que convert_to_lambda
est faux, car il ne fonctionne pas avec class_exec
.
est ici solution possible:
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"
devrait fonctionner sur Ruby 2.1+
Merci! Très utile :) Le fait que define_method a finalement produit un lambda était ce qui a provoqué ma confusion. –
Fun question de temps: comment faites-vous cela dans jruby? – Schneems
Réponse à ma question amusante: http://stackoverflow.com/questions/13239338/convert-bloc-to-lambda-in-jruby – Schneems