Quelle est la différence entre un proc et un lambda dans Ruby?

Et quand utiliseriez-vous l’un plutôt que l’autre?

Une différence réside dans la façon dont ils traitent les arguments. Créer un proc en utilisant proc {} et Proc.new {} sont équivalents. Cependant, l’utilisation de lambda {} vous donne un proc qui vérifie le nombre d’arguments transmis. Du ri Kernel#lambda :

Équivalent à Proc.new , sauf que les objects Proc résultants vérifient le nombre de parameters transmis lors de l’appel.

Un exemple:

 p = Proc.new {|a, b| puts a**2+b**2 } # => # p.call 1, 2 # => 5 p.call 1 # => NoMethodError: undefined method `**' for nil:NilClass p.call 1, 2, 3 # => 5 l = lambda {|a, b| puts a**2+b**2 } # => # l.call 1, 2 # => 5 l.call 1 # => ArgumentError: wrong number of arguments (1 for 2) l.call 1, 2, 3 # => ArgumentError: wrong number of arguments (3 for 2) 

De plus, comme Ken le fait remarquer, l’utilisation de return dans un lambda renvoie la valeur de ce lambda, mais l’utilisation de return dans un proc revient du bloc englobant.

 lambda { return :foo }.call # => :foo return # => LocalJumpError: unexpected return Proc.new { return :foo }.call # => LocalJumpError: unexpected return 

Donc, pour la plupart des utilisations rapides, elles sont identiques, mais si vous voulez une vérification automatique ssortingcte des arguments (ce qui peut parfois aider au débogage), ou si vous devez utiliser l’instruction return pour renvoyer la valeur de proc, utilisez lambda .

La vraie différence entre procs et lambdas a tout à voir avec les mots-clés de stream de contrôle. Je parle de return , raise , break , redo , retry etc. – ces mots de contrôle. Disons que vous avez une déclaration de retour dans un proc. Lorsque vous appelez votre proc, il va non seulement vous en extraire, mais vous revenez également de la méthode par exemple:

 def my_method puts "before proc" my_proc = Proc.new do puts "inside proc" return end my_proc.call puts "after proc" end my_method shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb before proc inside proc 

La dernière mise dans la méthode n’a jamais été exécutée, car lorsque nous avons appelé notre proc, le return intérieur de la méthode nous a déchargés de la méthode. Si, toutefois, nous convertissons notre proc en lambda, nous obtenons ce qui suit:

 def my_method puts "before proc" my_proc = lambda do puts "inside proc" return end my_proc.call puts "after proc" end my_method shoaib@shoaib-ubuntu-vm:~/tmp$ ruby a.rb before proc inside proc after proc 

Le retour dans le lambda ne nous extrait que de la lambda elle-même et la méthode englobante continue à s’exécuter. La façon dont les mots-clés du stream de contrôle sont traités dans procs et lambdas est la principale différence entre eux

C’est un peu subtil. Ce sont les deux méthodes qui créent des fermetures et les deux renvoient des objects Proc. Il y a aussi une troisième façon – Proc.new . La différence réside dans leur comportement, et les spécificités dépendent de l’utilisation de Ruby 1.8 ou 1.9 (en fait, il existe une autre façon de les créer dans Ruby 1.9). Dans le cas général, la différence n’est pas quelque chose dont vous devez vous soucier. Ce n’est que lorsque vous êtes soucieux de rigueur que cela fait une différence. Quand utiliser lambda, quand utiliser Proc.new? couvre assez bien les différences.

De manière générale, les lambdas sont plus intuitifs que procs car ils sont plus proches des méthodes. Ils sont assez ssortingcts au sujet de l’arité, et ils sortent simplement quand vous appelez le retour. Pour cette raison, de nombreux Rubyistes utilisent le lambda comme premier choix, à moins qu’ils aient besoin des fonctionnalités spécifiques de procs.

Procs: Objets de la classe Proc . Comme les blocs, ils sont évalués dans la scope où ils sont définis. Lambdas: Également des objects de classe Proc mais subtilement différents des processus réguliers. Ce sont des fermetures comme des blocs et des procs, et en tant que telles, elles sont évaluées dans la scope où elles sont définies.

Création de Proc

 a = Proc.new { |x| x 2 } 

Création de lambda

b = lambda { |x| x 2 b = lambda { |x| x 2 }

Voici une autre façon de comprendre cela.

Un bloc est un bloc de code attaché à l’invocation d’un appel d’une méthode sur un object. Dans l’exemple ci-dessous, self est une instance d’une classe anonyme héritant d’ActionView :: Base dans la structure Rails (qui comprend elle-même de nombreux modules d’aide). la carte est une méthode que nous appelons sur soi-même. Nous passons un argument à la méthode et nous attachons toujours le bloc à la fin de l’invocation de la méthode:

 self.card :contacts do |c| // a chunk of valid ruby code end 

Ok, nous passons un morceau de code à une méthode. Mais comment pouvons-nous utiliser ce bloc? Une option consiste à convertir le morceau de code en object. Ruby propose trois méthodes pour convertir un morceau de code en object

 # lambda > l = lambda { |a| a + 1 } > l.call(1) => 2 # Proc.new > l2= Proc.new { |a| a + 1 } > l2.call(1) => 2 # & as the last method argument with a local variable name def add(&block) end 

Dans la méthode ci-dessus, le & convertit le bloc passé à la méthode en object et stocke cet object dans le bloc de variable local. En fait, nous pouvons montrer qu’il a le même comportement que lambda et Proc.new:

 def add(&block) block end l3 = add { |a| a + 1 } l3.call(1) => 2 

C’est important. Lorsque vous transmettez un bloc à une méthode et que vous le convertissez en utilisant &, l’object qu’il crée utilise Proc.new pour effectuer la conversion.

Notez que j’ai évité l’utilisation de “proc” en option. C’est parce que c’est Ruby 1.8, c’est le même que lambda et dans Ruby 1.9, c’est la même chose que Proc.new et dans toutes les versions de Ruby, cela devrait être évité.

Alors, vous demandez quelle est la différence entre lambda et Proc.new?

Premièrement, en termes de passage de paramètre, lambda se comporte comme un appel de méthode. Il déclenchera une exception si vous transmettez le nombre incorrect d’arguments. En revanche, Proc.new se comporte comme une affectation parallèle. Tous les arguments non utilisés sont convertis en zéro:

 > l = lambda {|a,b| puts "#{a} + #{b}" } => # > l.call(1) ArgumentError: wrong number of arguments (1 for 2) > l2 = Proc.new {|a,b| puts "#{a} + #{b}" } => # > l2.call(1) 1 + 

Deuxièmement, lambda et Proc.new traitent le mot-clé de retour différemment. Lorsque vous effectuez un retour à l’intérieur de Proc.new, il renvoie en fait de la méthode englobante, c’est-à-dire du contexte environnant. Lorsque vous revenez d’un bloc lambda, il retourne simplement du bloc, et non de la méthode englobante. Fondamentalement, il quitte l’appel au bloc et continue l’exécution avec le rest de la méthode englobante.

 > def add(a,b) l = Proc.new { return a + b} l.call puts "now exiting method" end > add(1,1) => 2 # NOTICE it never prints the message "now exiting method" > def add(a,b) l = lambda { return a + b } l.call puts "now exiting method" end > add(1,1) => now exiting method # NOTICE this time it prints the message "now exiting method" 

Alors pourquoi cette différence de comportement? La raison en est que, avec Proc.new, nous pouvons utiliser des iterators dans le contexte des méthodes englobantes et en tirer des conclusions logiques. Regardez cet exemple:

 > def print(max) [1,2,3,4,5].each do |val| puts val return if val > max end end > print(3) 1 2 3 4 

Nous nous attendons à ce que lorsque nous invoquons le retour dans l’iterator, il revienne de la méthode englobante. Rappelez-vous que les blocs passés aux iterators sont convertis en objects à l’aide de Proc.new et c’est pourquoi, lorsque nous utilisons return, il va quitter la méthode englobante.

Vous pouvez considérer les lambdas comme des méthodes anonymes, ils isolent des blocs de code individuels dans un object qui peut être traité comme une méthode. En fin de compte, penser à un lambda se comporter comme une méthode anomyous et Proc.new se comporter comme un code en ligne.

Il n’y a que deux différences principales.

  • Tout d’abord, un lambda vérifie le nombre d’arguments qui lui sont transmis, contrairement à un proc . Cela signifie qu’un lambda générera une erreur si vous lui transmettez le mauvais nombre d’arguments, alors qu’un proc ignorera les arguments inattendus et assignera nil à tous les arguments manquants.
  • Deuxièmement, lorsqu’un lambda retourne, il renvoie le contrôle à la méthode appelante; lorsqu’un proc revient, il le fait immédiatement, sans revenir à la méthode d’appel.

Pour voir comment cela fonctionne, regardez le code ci-dessous. Notre première méthode appelle un proc ; le second appelle un lambda .

 def batman_ironman_proc victor = Proc.new { return "Batman will win!" } victor.call "Iron Man will win!" end puts batman_ironman_proc # prints "Batman will win!" def batman_ironman_lambda victor = lambda { return "Batman will win!" } victor.call "Iron Man will win!" end puts batman_ironman_lambda # prints "Iron Man will win!" 

Voyez comment le proc dit “Batman va gagner!”, Car il retourne immédiatement, sans revenir à la méthode batman_ironman_proc.

Notre lambda , cependant, retourne dans la méthode après avoir été appelée, ainsi la méthode retourne le dernier code évalué: “Iron Man va gagner!”

Les différences entre proc et lambda sont que proc n’est qu’une copie de code avec les arguments remplacés à leur tour, alors que lambda est une fonction comme dans d’autres langages. (comportement du retour, vérification des arguments)