rails - ruby procs and lambdas




Quand utiliser lambda, quand utiliser Proc.new? (10)

Il vaut la peine de souligner que le return dans un proc retourne de la méthode englobant lexicalement, c’est -à- dire la méthode où le proc a été créé , pas la méthode qui l’appelle. Ceci est une conséquence de la propriété de fermeture de procs. Donc, le code suivant ne génère rien:

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

Bien que le proc s'exécute dans foobar , il a été créé dans foo et donc le return sort de foo , pas seulement foobar . Comme Charles Caldwell l'a écrit ci-dessus, le sentiment est GOTO. À mon avis, le return est correct dans un bloc qui est exécuté dans son contexte lexical, mais est beaucoup moins intuitif lorsqu'il est utilisé dans un proc exécuté dans un contexte différent.

En Ruby 1.8, il existe des différences subtiles entre proc / lambda d'une part et Proc.new de l'autre.

  • Quelles sont ces différences?
  • Pouvez-vous donner des directives sur la façon de choisir lequel choisir?
  • Dans Ruby 1.9, proc et lambda sont différents. Quel est le problème?

J'ai trouvé cette page qui montre quelle est la différence entre Proc.new et lambda . Selon cette page, la seule différence est qu'un lambda est strict quant au nombre d'arguments acceptés, alors que Proc.new convertit les arguments manquants en éléments nil . Voici un exemple de session IRB illustrant la différence:

irb(main):001:0> l = lambda { |x, y| x + y }
=> #<Proc:[email protected](irb):1>
irb(main):002:0> p = Proc.new { |x, y| x + y }
=> #<Proc:[email protected](irb):2>
irb(main):003:0> l.call "hello", "world"
=> "helloworld"
irb(main):004:0> p.call "hello", "world"
=> "helloworld"
irb(main):005:0> l.call "hello"
ArgumentError: wrong number of arguments (1 for 2)
    from (irb):1
    from (irb):5:in `call'
    from (irb):5
    from :0
irb(main):006:0> p.call "hello"
TypeError: can't convert nil into String
    from (irb):2:in `+'
    from (irb):2
    from (irb):6:in `call'
    from (irb):6
    from :0

La page recommande également d’utiliser lambda sauf si vous souhaitez spécifiquement le comportement tolérant aux erreurs. Je suis d'accord avec ce sentiment. Utiliser un lambda semble un peu plus concis, et avec une différence aussi insignifiante, il semble être le meilleur choix dans la situation moyenne.

Quant à Ruby 1.9, désolé, je n’ai pas encore examiné la version 1.9, mais je n’imagine pas qu’ils la changeraient trop (ne me croyez pas sur parole cependant, il semble que vous ayez entendu parler de certains changements, alors Je me trompe probablement là-bas).


Je ne peux pas en dire beaucoup sur les différences subtiles. Cependant, je peux souligner que Ruby 1.9 autorise maintenant des paramètres optionnels pour les lambdas et les blocs.

Voici la nouvelle syntaxe pour les stabby lambdas sous 1.9:

stabby = ->(msg='inside the stabby lambda') { puts msg }

Ruby 1.8 n'avait pas cette syntaxe. La méthode conventionnelle de déclaration de blocs / lambdas ne supportait pas non plus les arguments optionnels:

# under 1.8
l = lambda { |msg = 'inside the stabby lambda'|  puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'|  puts msg }

Ruby 1.9, cependant, supporte les arguments optionnels même avec l'ancienne syntaxe:

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:[email protected](irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

Si vous voulez construire Ruby1.9 pour Leopard ou Linux, consultez cet article (promotion sans vergogne).


Je suis un peu en retard sur ce point, mais il y a une chose importante mais peu connue à propos de Proc.new n'est pas mentionnée du tout dans les commentaires. Comme par documentation :

Proc::new peut être appelé sans bloc uniquement dans une méthode avec un bloc attaché, auquel cas ce bloc est converti en objet Proc .

Cela dit, Proc.new permet d'enchaîner les méthodes de production:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!

Pour élaborer sur la réponse de Guy Accordéon:

Notez que Proc.new crée un Proc.new sortant en passant un bloc. Je crois que lambda {...} est analysé comme une sorte de littéral, plutôt que comme un appel de méthode qui passe un bloc. return depuis l'intérieur d'un bloc attaché à un appel de méthode retournera par la méthode, pas par le bloc, et le cas Proc.new est un exemple.

(Ceci est 1.8. Je ne sais pas comment cela se traduit par 1.9.)


Pour fournir des éclaircissements supplémentaires:

Joey dit que le comportement de retour de Proc.new est surprenant. Cependant, lorsque vous considérez que Proc.new se comporte comme un bloc, cela n’est pas surprenant, car c’est exactement comme cela que les blocs se comportent. Les lambas, par contre, se comportent davantage comme des méthodes.

Cela explique en fait pourquoi les Procs sont flexibles en matière d'arité (nombre d'arguments) alors que les lambdas ne le sont pas. Les blocs ne nécessitent pas que tous leurs arguments soient fournis mais les méthodes le font (sauf si une valeur par défaut est fournie). Bien que fournir l'argument lambda par défaut ne soit pas une option dans Ruby 1.8, il est désormais pris en charge dans Ruby 1.9 avec la syntaxe alternative lambda (comme indiqué par webmat):

concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1)   # => "12"

Et Michiel de Mare (l'OP) a tort à propos des Procs et de lambda se comportant de la même manière avec l'arity dans Ruby 1.9. J'ai vérifié qu'ils maintiennent toujours le comportement de 1.8 comme spécifié ci-dessus.

break déclarations de break n'ont pas beaucoup de sens, que ce soit dans Procs ou lambdas. Dans Procs, la pause vous renverrait depuis Proc.new qui a déjà été complétée. Et cela n’a aucun sens de rompre avec un lambda car c’est essentiellement une méthode, et vous ne sortiriez jamais du niveau supérieur d’une méthode.

next , redo et relances se comportent de la même manière dans Procs et Lambdas. Considérant que retry n'est pas autorisé dans l'un ou l'autre et lèvera une exception.

Enfin, la méthode proc ne doit jamais être utilisée car elle est incohérente et a un comportement inattendu. En Ruby 1.8, il retourne un lambda! En Ruby 1.9, ceci a été corrigé et renvoie un Proc. Si vous voulez créer un Proc, Proc.new avec Proc.new .

Pour plus d'informations, je recommande vivement le langage de programmation The Ruby de O'Reilly, qui est ma source pour la plupart de ces informations.


Réponse courte: Ce qui compte, c’est ce que fait le return : lambda retourne hors de lui-même et proc retourne hors de lui-même ET de la fonction qui l’appelle.

Ce qui est moins clair, c'est pourquoi vous voulez utiliser chacun d'eux. lambda est ce que nous attendons des choses devraient être faites dans un sens de programmation fonctionnelle Il s'agit essentiellement d'une méthode anonyme avec la portée actuelle automatiquement liée. Lambda est celui que vous devriez probablement utiliser.

En revanche, Proc est vraiment utile pour implémenter le langage lui-même. Par exemple, vous pouvez implémenter des instructions "if" ou "for". Tout retour trouvé dans le proc retournera par la méthode qui l'a appelé, pas simplement par l'instruction "if". Voici comment les langues fonctionnent, comment "if" fonctionne, donc je suppose que Ruby l'utilise sous les couvertures et qu'elle vient juste de l'exposer car elle semblait puissante.

Vous n’auriez vraiment besoin de cela que si vous créez de nouvelles constructions de langage telles que des boucles, des constructions if-else, etc.


Une autre différence importante mais subtile entre les procs créés avec lambda et les procs créés avec Proc.new est la façon dont ils gèrent l'instruction de return :

  • Dans une procédure lambda -created, l'instruction return renvoie uniquement à partir de la procédure elle-même.
  • Dans une Proc.new -created, l'instruction return est un peu plus surprenante: elle renvoie le contrôle non seulement à partir de la procédure, mais également à partir de la méthode englobant la procédure!

Voici le return du proc créé par lambda en action. Il se comporte d'une manière que vous attendez probablement:

def whowouldwin

  mylambda = lambda {return "Freddy"}
  mylambda.call

  # mylambda gets called and returns "Freddy", and execution
  # continues on the next line

  return "Jason"

end


whowouldwin
#=> "Jason"

Maintenant, voici le Proc.new un Proc.new procré faisant la même chose. Vous êtes sur le point de voir un de ces cas où Ruby enfreint le principe tant vanté de la moindre surprise:

def whowouldwin2

  myproc = Proc.new {return "Freddy"}
  myproc.call

  # myproc gets called and returns "Freddy", 
  # but also returns control from whowhouldwin2!
  # The line below *never* gets executed.

  return "Jason"

end


whowouldwin2         
#=> "Freddy"

Grâce à ce comportement surprenant (ainsi qu’à moins de dactylographie), j’ai tendance à préférer utiliser lambda à Proc.new lorsqu’on fabrique des procs.


lambda fonctionne comme prévu, comme dans d'autres langues.

Le Proc.new câblé est surprenant et déroutant.

L'instruction de return dans proc créée par Proc.new non seulement le contrôle de lui-même, mais également de la méthode qui l'englobe .

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

Vous pouvez affirmer que Proc.new insère du code dans la méthode englobante, exactement comme block. Mais Proc.new crée un objet, alors que les blocs font partie d’ un objet.

Et il y a une autre différence entre lambda et Proc.new , qui est leur traitement des arguments (erronés). lambda s'en plaint, alors que Proc.new ignore les arguments supplémentaires ou considère l'absence d'arguments comme nulle.

irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:[email protected](irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:[email protected](irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
        from (irb):21:in `block in irb_binding'
        from (irb):25:in `call'
        from (irb):25
        from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
        from (irb):47:in `block in irb_binding'
        from (irb):49:in `call'
        from (irb):49
        from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"

BTW, proc dans Ruby 1.8 crée un lambda, alors que dans Ruby 1.9+ se comporte comme Proc.new , ce qui est vraiment déroutant.








proc