Question

Dans 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 lignes directrices sur la façon de décider lequel choisir ?
  • Dans Ruby 1.9, proc et lambda sont différents.Quel est le problème?
Était-ce utile?

La solution

Une autre différence importante mais subtile entre les processus créés avec lambda et procs créés avec Proc.new c'est ainsi qu'ils gèrent le return déclaration:

  • Dans un lambda-procédé créé, le return l'instruction revient uniquement à partir du processus lui-même
  • Dans un Proc.new-procédé créé, le return la déclaration est un peu plus surprenante :il renvoie le contrôle non seulement depuis le processus, mais aussi de la méthode contenant le proc!

Voici lambda-créé des procédures return en action.Il se comporte d'une manière à laquelle vous 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.new-créé des procédures return faire la même chose.Vous êtes sur le point de voir l'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'à une frappe moindre), j'ai tendance à privilégier l'utilisation lambda sur Proc.new lors de la réalisation de procédures.

Autres conseils

Pour apporter des précisions supplémentaires :

Joey dit que le comportement de retour de Proc.new est surprenant.Cependant, si l'on considère que Proc.new se comporte comme un bloc, cela n'est pas surprenant car c'est exactement ainsi que se comportent les blocs.les lambas, en revanche, 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 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 le fait que les Procs et lambda se comportent de la même manière avec arité dans Ruby 1.9.J'ai vérifié qu'ils conservent toujours le comportement de la version 1.8 comme spécifié ci-dessus.

break les déclarations n'ont pas vraiment de sens dans Procs ou lambdas.Dans Procs, la pause vous renverrait de Proc.new qui est déjà terminé.Et cela n'a aucun sens de rompre avec un lambda puisqu'il s'agit essentiellement d'une méthode, et vous ne romprez jamais avec le niveau supérieur d'une méthode.

next, redo, et raise se comportent de la même manière dans les Procs et les lambdas.Alors que retry n’est autorisé ni dans l’un ni dans l’autre et soulèvera une exception.

Et enfin, le proc La méthode ne doit jamais être utilisée car elle est incohérente et a un comportement inattendu.Dans Ruby 1.8, il renvoie en fait un lambda !Dans Ruby 1.9, cela a été corrigé et renvoie un Proc.Si vous souhaitez créer un Proc, restez fidèle à Proc.new.

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

j'ai trouvé cette page ce qui montre quelle est la différence entre Proc.new et lambda sont.Selon la page, la seule différence est qu'un lambda est strict sur le nombre d'arguments qu'il accepte, alors que Proc.new convertit les arguments manquants en nil.Voici un exemple de session IRB illustrant la différence :

irb(main):001:0> l = lambda { |x, y| x + y }
=> #<Proc:0x00007fc605ec0748@(irb):1>
irb(main):002:0> p = Proc.new { |x, y| x + y }
=> #<Proc:0x00007fc605ea8698@(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 être le meilleur choix dans une situation moyenne.

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

Proc est plus ancien, mais la sémantique du retour me semble très contre-intuitive (du moins lorsque 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), ce qui est fondamentalement un incontournable et de nature hautement non fonctionnelle.

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

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 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 les blocs/lambdas ne prenait 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 souhaitez compiler Ruby1.9 pour Leopard ou Linux, consultez Cet article (auto-promotion éhontée).

Réponse courte:Ce qui compte c'est quoi return fait:lambda revient hors de lui-même, et proc revient hors de lui-même ET de 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 le sens de la 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 proc renverra la méthode qui l'a appelé, pas seulement l'instruction "if".C'est ainsi que fonctionnent les langages, comment fonctionnent les instructions "if", donc je suppose que Ruby l'utilise sous les couvertures et ils l'ont simplement 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.

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 les Procs peuvent être considérés comme exécutés en ligne avec la méthode appelante, au moins c'est un bon moyen de décider laquelle utiliser. dans chaque cas.

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

Voici un exemple assez verbeux 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

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

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

Le filaire Proc.new est surprenant et déroutant.

Le return instruction dans proc créée par Proc.new ne rendra pas seulement le contrôle à lui-même, mais également de la méthode qui le contient.

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, tout comme block.Mais Proc.new crée un objet, tandis que les blocs sont partie de un objet.

Et il y a une autre différence entre lambda et Proc.new, qui est leur gestion des (faux) arguments.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: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"

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

Pour développer la réponse d'Accordion Guy :

Remarquerez que Proc.new crée un processus en recevant un bloc.Je crois que lambda {...} est analysé comme une sorte de littéral, plutôt que comme un appel de méthode qui transmet un bloc. returning depuis l'intérieur d'un bloc attaché à un appel de méthode reviendra de la méthode, pas du bloc, et le Proc.new Cette affaire en est un exemple.

(C'est 1,8.Je ne sais pas comment cela se traduit par 1.9.)

Je suis un peu en retard à ce sujet, mais il y a une chose géniale mais peu connue à propos de Proc.new pas du tout mentionné dans les commentaires.Comme par Documentation:

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

Cela dit, Proc.new permet 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!

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

Cela vaut la peine de souligner que return dans un proc revient à partir de la méthode lexicalement englobante, c'est-à-dire la méthode avec laquelle le processus a été créé, pas la méthode qui a appelé le proc.C'est une conséquence de la propriété de fermeture de procs.Le code suivant ne génère donc rien :

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

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

foo

Bien que le processus s'exécute dans foobar, il a été créé en foo et donc le return sorties foo, pas seulement foobar.Comme Charles Caldwell l'a écrit ci-dessus, il y a une sensation GOTO.À mon avis, return convient parfaitement dans un bloc exécuté dans son contexte lexical, mais est beaucoup moins intuitif lorsqu'il est utilisé dans un processus exécuté dans un contexte différent.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top