ruby - Quand utiliser lambda, quand utiliser Proc.new?

Translate

Dans Ruby 1.8, il existe des différences subtiles entre proc / lambda d'une part, etProc.newde l'autre.

  • Quelles sont ces différences?
  • Pouvez-vous donner des directives sur la façon de décider lequel choisir?
  • Dans Ruby 1.9, proc et lambda sont différents. Quel est le problème?
This question and all comments follow the "Attribution Required."

Toutes les réponses

Translate

Une autre différence importante mais subtile entre les procs créés aveclambdaet procs créés avecProc.newest comment ils gèrent lereturndéclaration:

  • Dans unlambda-créé proc, lereturnl'instruction ne renvoie que du proc lui-même
  • Dans unProc.new-créé proc, lereturnL'instruction est un peu plus surprenante: elle renvoie le contrôle non seulement du proc,mais aussi de la méthode englobant le proc!

Voicilambda-créé procreturnen 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 unProc.new-créé procreturnfaire la même chose. Vous êtes sur le point de voir l'un de ces cas où Ruby brise 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 frappe), j'ai tendance à privilégier l'utilisationlambdaplus deProc.newlors de la création de procs.

La source
Translate

Pour apporter des précisions supplémentaires:

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

Cela explique en fait pourquoi les Procs sont flexibles en termes 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 lambda alternative (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) se trompe sur le comportement des Procs et lambda avec l'arité dans Ruby 1.9. J'ai vérifié qu'ils maintiennent toujours le comportement de 1.8 comme spécifié ci-dessus.

breakLes instructions n'ont en fait pas beaucoup de sens dans Procs ou lambdas. Dans Procs, la pause vous ramènerait de Proc.new qui est déjà terminé. Et cela n'a aucun sens de rompre avec un lambda car il s'agit essentiellement d'une méthode, et vous ne rompriez jamais avec le niveau supérieur d'une méthode.

next, redo, etraisese comportent de la même manière dans les processus et les lambdas. Tandis queretryn'est pas autorisé dans les deux et lèvera une exception.

Et enfin, leprocne doit jamais être utilisée car elle est incohérente et présente un comportement inattendu. Dans Ruby 1.8, il renvoie en fait un lambda! Dans Ruby 1.9, cela a été corrigé et il renvoie un Proc. Si vous souhaitez créer un Proc, restez avecProc.new.

Pour plus d'informations, je recommande vivement O'Reilly'sLe langage de programmation Rubyqui est ma source pour la plupart de ces informations.

La source
Translate

j'ai trouvécette pagequi montre quelle est la différence entreProc.newetlambdasont. Selon la page, la seule différence est qu'un lambda est strict sur le nombre d'arguments qu'il accepte, alors queProc.newconvertit les arguments manquants ennil. 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 de tolérance aux erreurs. Je suis d'accord avec ce sentiment. Utiliser un lambda semble un peu plus concis, et avec une différence aussi insignifiante, cela semble le meilleur choix dans la situation moyenne.

En ce qui concerne Ruby 1.9, désolé, je n'ai pas encore examiné la version 1.9, mais je n'imagine pas qu'ils le changeraient beaucoup (ne me croyez pas sur parole, il semble que vous ayez entendu parler de certains changements, donc Je me trompe probablement).

La source
Louise Lee
Translate

Proc est plus ancien, mais la sémantique du retour est très contre-intuitive pour moi (du moins lorsque j'apprenais la langue) car:

  1. Si vous utilisez proc, vous utilisez probablement une sorte de paradigme fonctionnel.
  2. Proc peut retourner hors de la portée englobante (voir les réponses précédentes), ce qui est fondamentalement un goto et de nature hautement non fonctionnelle.

Lambda est fonctionnellement plus sûr et plus facile à raisonner - je l'utilise toujours à la place de proc.

La source
Translate

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

Voici la nouvelle syntaxe pour les lambdas stabby sous 1.9:

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

Ruby 1.8 n'avait pas cette syntaxe. La manière conventionnelle de déclarer des 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, prend en charge 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, consultezCet article(auto-promotion éhontée).

La source
Translate

Réponse courte: ce qui compte, c'est quoireturnfait: lambda retourne hors de lui-même et proc retourne hors de lui-même ET la fonction qui l'a appelé.

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

Proc, en revanche, est vraiment utile pour implémenter le langage lui-même. Par exemple, vous pouvez implémenter des instructions "if" ou des boucles "for" avec elles. Tout retour trouvé dans le processus retournera à partir de la méthode qui l'a appelé, pas seulement l'instruction "if". C'est ainsi que fonctionnent les langages, comment fonctionnent les instructions "si", donc je suppose que Ruby l'utilise sous les couvertures et ils l'ont juste exposé parce que cela semblait puissant.

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

La source
Translate

Un bon moyen de le voir est que les lambdas sont exécutées dans leur propre portée (comme s'il s'agissait d'un appel de méthode), tandis que Procs peut être considéré comme exécuté en ligne avec la méthode appelante, du moins c'est un bon moyen de décider lequel utiliser dans chaque cas.

La source
Translate

Je n'ai remarqué aucun commentaire sur la troisième méthode dans la quête, "proc" qui est obsolète, mais gérée différemment en 1.8 et 1.9.

Voici un exemple assez détaillé qui permet de voir facilement les différences entre les trois appels similaires:

def meth1
  puts "method start"

  pr = lambda { return }
  pr.call

  puts "method end"  
end

def meth2
  puts "method start"

  pr = Proc.new { return }
  pr.call

  puts "method end"  
end

def meth3
  puts "method start"

  pr = proc { return }
  pr.call

  puts "method end"  
end

puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3
La source
Geoffrey Lee
Translate

Fermetures en Rubyest un bon aperçu du fonctionnement des blocs, lambda et proc dans Ruby, avec Ruby.

La source
Translate

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

Le filaireProc.newest surprenant et déroutant.

lereturninstruction dans proc créée parProc.newretournera non seulement le contrôle de lui-même, maisaussi 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 dire queProc.newinsère du code dans la méthode englobante, tout comme block. MaisProc.newcrée un objet, tandis que les blocs sontpartie deun objet.

Et il y a une autre différence entre lambda etProc.new, qui est leur gestion des (mauvais) arguments. lambda s'en plaint, tandis queProc.newignore 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,procdans Ruby 1.8 crée un lambda, tandis que dans Ruby 1.9+ se comporte commeProc.new, ce qui est vraiment déroutant.

La source
Translate

Pour élaborer sur la réponse de Accordion Guy:

Remarquerez queProc.newcrée un processus en passant un bloc. je crois quelambda {...}est analysé comme une sorte de littéral, plutôt que comme un appel de méthode qui passe un bloc.returnde l'intérieur d'un bloc attaché à un appel de méthode retournera de la méthode, pas du bloc, et leProc.newcas est un exemple de ceci en jeu.

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

La source
Translate

Je suis un peu en retard à ce sujet, mais il y a une chose géniale mais peu connue à propos deProc.newpas du tout mentionné dans les commentaires. Comme parDocumentation:

Proc::newpeut être appelé sans bloc uniquement dans une méthode avec un bloc attaché, auquel cas celabloc est converti enProcobjet.

Cela dit,Proc.newpermet 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!
La source
Translate

La différence de comportement avecreturnest à mon humble avis la différence la plus importante entre les 2. Je préfère aussi lambda parce que c'est moins typant que Proc.new :-)

La source
Translate

Cela vaut la peine de souligner quereturndans un proc retourne de la méthode englobante lexicale, iela méthode où le proc a été créé, ne pasla méthode qui a appelé le proc. Ceci est une conséquence de la propriété de fermeture de procs. Ainsi, le code suivant ne produit 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 dansfoobar, il a été créé enfooet ainsi lereturnsortiesfoo, pas seulementfoobar. Comme Charles Caldwell l'a écrit ci-dessus, il a une sensation GOTO. À mon avis,returnest bien dans un bloc qui est exécuté dans son contexte lexical, mais est beaucoup moins intuitif lorsqu'il est utilisé dans un proc qui est exécuté dans un contexte différent.

La source