2010-06-01 10 views
13

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! 

Répondre

20

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 
+0

Merci! Très utile :) Le fait que define_method a finalement produit un lambda était ce qui a provoqué ma confusion. –

+3

Fun question de temps: comment faites-vous cela dans jruby? – Schneems

+1

Réponse à ma question amusante: http://stackoverflow.com/questions/13239338/convert-bloc-to-lambda-in-jruby – Schneems

0

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.

4

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.

3

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+

Questions connexes