Quand utiliser lambda, quand utiliser Proc.new?

336

Dans Ruby 1.8, il existe des différences subtiles entre proc / lambda d'une part et Proc.newd'autre part.

  • 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?
Michiel de Mare
la source
3
Voir aussi: le livre Ruby Programming Language de Matz et Flanagan, il a couvert ce sujet de manière exhaustive. proc se comporte comme une sémantique de bloc - rendement, tandis que lambda se comporte comme une méthode - méthode appelle la sémantique. Revenez également, coupez, et. tous se comportent différemment dans procs n lambdas
Gishu
1
Voir également un article détaillé sur les différences de flux de contrôle entre Ruby Procs et Lambdas
Akshay Rawat
vous avez accepté la réponse qui dit seulement quelle est la différence entre proc et lambda, tandis que le titre de votre question est de savoir quand utiliser ces choses
Shri

Réponses:

378

Une autre différence importante mais subtile entre les procs créés avec lambdaet procs créés avec Proc.newest la façon dont ils gèrent l' returninstruction:

  • Dans un lambdaproc -created, l' returninstruction ne retourne que du proc lui-même
  • Dans un Proc.newproc -created, l' returninstruction est un peu plus surprenante: elle retourne le contrôle non seulement du proc, mais aussi de la méthode enfermant le proc!

Voici les lambdaprocs créés returnen 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 un Proc.newproc crééreturn fait 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'utilisation de lambdaover Proc.newlors de la création de procs.

Joey deVilla
la source
12
Ensuite, il y a aussi la procméthode. Est-ce juste un raccourci pour Proc.new?
panzi
6
@panzi, oui, procéquivaut àProc.new
ma11hew28
4
@mattdipasquale Dans mes tests, procagit comme lambdaet non comme Proc.newen ce qui concerne les déclarations de retour. Cela signifie que le document ruby ​​est inexact.
Kelvin
31
@mattdipasquale Désolé, je n'avais qu'à moitié raison. procagit comme lambdadans 1.8, mais agit comme Proc.newdans 1.9. Voir la réponse de Peter Wagenet.
Kelvin
55
Pourquoi ce comportement "surprenant"? A lambdaest une méthode anonyme. Puisqu'il s'agit d'une méthode, elle renvoie une valeur, et la méthode qui l'a appelée peut en faire ce qu'elle veut, y compris l'ignorer et renvoyer une valeur différente. A, Procc'est comme coller dans un extrait de code. Cela n'agit pas comme une méthode. Ainsi, lorsqu'un retour se produit dans le Proc, c'est juste une partie du code de la méthode qui l'a appelé.
Arcolye
96

Pour apporter des précisions 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 la façon dont 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 matière d'arité (nombre d'arguments), contrairement aux lambdas. Les blocs ne nécessitent pas que tous leurs arguments soient fournis, contrairement aux méthodes (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 maintenant 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) a tort sur les Procs et lambda se comportant de la même manière 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 renverrait de Proc.new qui a déjà été effectué. Et cela n'a aucun sens de rompre avec un lambda car c'est essentiellement une méthode, et vous ne vous sépareriez jamais du niveau supérieur d'une méthode.

next, redoEt raiseont le même comportement dans les deux procs et lambdas. Alors que ce retryn'est pas autorisé dans les deux cas et entraînera une exception.

Et enfin, la procméthode ne doit jamais être utilisée car elle est incohérente et a un comportement inattendu. Dans Ruby 1.8, il retourne 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 avec Proc.new.

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

Peter Wagenet
la source
1
"" "Cependant, lorsque vous considérez que Proc.new se comporte comme un bloc, cela n'est pas surprenant car c'est exactement la façon dont les blocs se comportent." "" <- le bloc fait partie d'un objet, tandis que Proc.new crée un objet. Lambda et Proc.new créent un objet dont la classe est Proc, pourquoi diff?
faible
1
A partir de Ruby 2.5, breakde relances procs LocalJumpError, alors que breakde lambdas de se comporte comme return( par exemple , return nil).
Masa Sakano
43

J'ai trouvé cette page qui montre quelle est la différence entre Proc.newet lambdasont. Selon la page, la seule différence est qu'un lambda est strict quant au nombre d'arguments qu'il accepte, alors qu'il Proc.newconvertit les arguments manquants en nil. Voici un exemple de session IRB illustrant la différence:

irb (principal): 001: 0> l = lambda {| x, y | x + y}
=> # <Proc: 0x00007fc605ec0748 @ (irb): 1>
irb (principal): 002: 0> p = Proc.new {| x, y | x + y}
=> # <Proc: 0x00007fc605ea8698 @ (irb): 2>
irb (principal): 003: 0> l.call "bonjour", "monde"
=> "helloworld"
irb (principal): 004: 0> p.call "bonjour", "monde"
=> "helloworld"
irb (principal): 005: 0> l.call "bonjour"
ArgumentError: nombre d'arguments incorrect (1 pour 2)
    de (irb): 1
    de (irb): 5: en `appel '
    de (irb): 5
    à partir de: 0
irb (principal): 006: 0> p.call "bonjour"
TypeError: impossible de convertir nil en chaîne
    de (irb): 2: en `+ '
    de (irb): 2
    de (irb): 6: en `appel '
    de (irb): 6
    à partir de: 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. L'utilisation d'un lambda semble un peu plus concise, et avec une différence aussi insignifiante, cela semble être le meilleur choix dans la situation moyenne.

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

Mike Stone
la source
2
procs retournent également différemment de lambdas.
Cam
"" "Proc.new convertit les arguments manquants en nil" "" Proc.new ignore également les arguments supplémentaires (bien sûr, lambda s'en plaint avec une erreur).
faible
16

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

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

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

Charles Caldwell
la source
11

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

Voici la nouvelle syntaxe des 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 les blocs / lambdas ne prend pas non plus en charge les arguments facultatifs:

# 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 facultatifs même avec l'ancienne syntaxe:

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:0x0e5dbc@(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 (auto promotion sans vergogne).

webmat
la source
Les paramètres facultatifs au sein de lambda étaient très nécessaires, je suis heureux qu'ils l'aient ajouté dans 1.9. Je suppose que les blocs peuvent également avoir des paramètres facultatifs (en 1.9)?
mpd
vous ne démontrez pas les paramètres par défaut dans les blocs, seulement les lambdas
iconoclaste
11

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

Ce qui est moins clair, c'est pourquoi vous voulez utiliser chacun d'eux. lambda est ce que nous attendons des choses dans le sens de la programmation fonctionnelle. Il s'agit essentiellement d'une méthode anonyme avec la portée actuelle 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 proc sortira de la méthode qui l'a appelé, pas seulement de l'instruction "if". C'est ainsi que les langues fonctionnent, comment les instructions "si" fonctionnent, donc je suppose que Ruby utilise cela sous les couvertures et ils l'ont juste exposé parce qu'il semblait puissant.

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

Evan Moran
la source
1
"lambda revient de lui-même, et proc revient de lui-même ET la fonction qui l'a appelé" est tout à fait erronée et un malentendu très courant. Un proc est une fermeture et revient de la méthode qui l'a créé. Voir ma réponse complète ailleurs sur la page.
ComDubh
10

Une bonne façon de le voir est que les lambdas sont exécutés 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 une bonne façon de décider laquelle utiliser. dans chaque cas.

krusty.ar
la source
8

Je n'ai remarqué aucun commentaire sur la troisième méthode dans le queston, "proc" qui est obsolète, mais géré 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
Dave Rapin
la source
1
Matz avait déclaré qu'il prévoyait de le déprécier car il était déroutant que proc et Proc.new retournent des résultats différents. En 1.9, ils se comportent de la même façon (proc est un alias de Proc.new). eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
Dave Rapin
@banister: a procretourné une lambda en 1.8; il a maintenant été corrigé pour retourner un proc en 1.9 - cependant c'est un changement de rupture; donc pas recommandé d'utiliser plus
Gishu
Je pense que la pioche dit dans une note de bas de page quelque part que proc est effectivement privé de quelque chose. Je n'ai pas le numéro de page exact.
dertoni
7

Les fermetures dans Ruby sont un bon aperçu du fonctionnement des blocs, lambda et proc dans Ruby, avec Ruby.

swrobel
la source
J'ai arrêté de lire ceci après avoir lu "une fonction ne peut pas accepter plusieurs blocs - violant le principe selon lequel les fermetures peuvent être transmises librement en tant que valeurs". Les blocs ne sont pas des fermetures. Les procs le sont et une fonction peut accepter plusieurs procs.
ComDubh
5

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

Le filaire Proc.newest surprenant et déroutant.

L' returninstruction dans proc créée par Proc.newrenverra non seulement le contrôle de lui-même, mais aussi de la méthode qui le renferme .

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

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

Vous pouvez faire valoir que le Proc.newcode insère dans la méthode englobante, tout comme le bloc. Mais Proc.newcrée un objet, tandis que le bloc fait partie d' un objet.

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

irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(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 comme Proc.new, ce qui est vraiment déroutant.

faible
la source
3

Pour développer la réponse de Guy Accordéon:

Notez que Proc.newcrée un proc out en passant un bloc. Je crois que lambda {...}c'est analysé comme une sorte de littéral, plutôt qu'un appel de méthode qui passe un bloc. returning de l'intérieur d'un bloc attaché à un appel de méthode reviendra de la méthode, pas du bloc, et le Proc.newcas en est un exemple.

(Ceci est 1,8. Je ne sais pas comment cela se traduit par 1,9.)

Peeja
la source
3

Je suis un peu en retard à ce sujet, mais il y a une grande chose, mais peu connue, qui Proc.newn'est pas mentionnée dans les commentaires. Comme par la documentation :

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

Cela dit, Proc.newpermet d'enchaîner les méthodes de rendement:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!
Aleksei Matiushkin
la source
Intéressant, il fait la même chose que déclarer un &blockargument dans le def, mais sans avoir à le faire dans la liste def arg.
jrochkind
2

Il vaut la peine de souligner que returndans un proc revient de la méthode lexicalement englobante, c'est -à- dire la méthode où le proc a été créé , pas la méthode qui l'a appelé. 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 dans foobar, il a été créé dans fooet ainsi les returnsorties foo, pas seulement foobar. Comme Charles Caldwell l'a écrit ci-dessus, il a une sensation GOTO. À mon avis, returnc'est 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.

ComDubh
la source
1

La différence de comportement avec returnest à mon humble avis la différence la plus importante entre les 2. Je préfère également lambda parce qu'il est moins typé que Proc.new :-)

Orion Edwards
la source
2
Pour mettre à jour: les procs peuvent maintenant être créés en utilisant proc {}. Je ne sais pas quand cela est entré en vigueur, mais c'est (légèrement) plus facile que d'avoir à taper Proc.new.
aceofbassgreg