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

176

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

Tom Lehman
la source
En plus de la réponse de jtbandes, il y a aussi une différence dans ce que la returndéclaration renvoie de par procrapport à lambda.
Ken Bloom
5
Voici un bon blog sur le même awaxman11.github.io/blog/2013/08/05/…
Arup Rakshit
2
Voici une réponse plus détaillée: stackoverflow.com/questions/626/…
Dan KK

Réponses:

260

Une différence réside dans la manière dont ils traitent les arguments. Créer un proc en utilisant proc {}et Proc.new {}sont équivalents. Cependant, utiliser lambda {}vous donne un proc qui vérifie le nombre d'arguments qui lui sont passés. De ri Kernel#lambda:

Équivalent à Proc.new , sauf que les objets Proc résultants vérifient le nombre de paramètres transmis lors de l'appel.

Un exemple:

p = Proc.new {|a, b| puts a**2+b**2 } # => #<Proc:0x3c7d28@(irb):1>
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 } # => #<Proc:0x15016c@(irb):5 (lambda)>
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 le fait remarquer Ken, utiliser à l' returnintérieur d'un lambda renvoie la valeur de ce lambda, mais utiliser returndans un proc renvoie à partir 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, ils sont les mêmes, mais si vous voulez une vérification automatique des arguments stricts (qui peut aussi parfois aider au débogage), ou si vous devez utiliser l' returninstruction pour renvoyer la valeur de proc, utilisez lambda.

jtbandes
la source
8
Serait-il exact de dire que les lambdas ressemblent beaucoup aux méthodes (vérifier les arguments et le retour en reviendra) tandis que les procs ressemblent beaucoup à des blocs (les arguments ne sont pas vérifiés et un retour sera renvoyé de la méthode contenant ou de lambda)?
pedz
Je suis allé à Dieu sait combien de sites Web et d'articles à l'heure actuelle et personne ne semble parler de l'utilité de Procs vs méthodes vs lambdas. Chaque explication fournit simplement un détail époustouflant sur la façon dont les valeurs de retour, etc., sont différentes, mais aucune sur pourquoi cela compte. Pour l'instant, je dois conclure qu'il s'agit d'un désordre de conception dans Ruby.
ankush981 le
76

La vraie différence entre procs et lambdas a tout à voir avec les mots-clés de flux de contrôle. Je parle return, raise, break, redo, retryetc. - ces mots de contrôle. Disons que vous avez une instruction return dans un proc. Lorsque vous appelez votre proc, il vous en sortira non seulement, mais reviendra également de la méthode englobante, 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

Le final putsde la méthode n'a jamais été exécuté, car lorsque nous avons appelé notre proc, celui- returnci nous a vidés de la méthode. Si, cependant, 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 nous vide uniquement du lambda lui-même et la méthode englobante continue de s'exécuter. La façon dont les mots-clés de flux de contrôle sont traités dans les procs et les lambdas est la principale différence entre eux

shoaib
la source
7

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

  • Tout d'abord, a lambdavérifie le nombre d'arguments qui lui sont passés, proccontrairement à a. Cela signifie que a lambdalancera une erreur si vous lui transmettez un nombre incorrect d'arguments, tandis que a procignorera les arguments inattendus et les assignera nilà ceux qui manquent.
  • Deuxièmement, quand un lambdaretourne, il repasse le contrôle à la méthode appelante; quand a procrevient, il le fait immédiatement, sans revenir à la méthode appelante.

Pour voir comment cela fonctionne, jetez un œil au code ci-dessous. Notre première méthode appelle a proc; le second appelle a 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 procdit "Batman va gagner!", C'est parce qu'il revient immédiatement, sans revenir à la méthode batman_ironman_proc.

Notre lambda, cependant, retourne dans la méthode après avoir été appelée, donc la méthode renvoie le dernier code qu'elle évalue: "Iron Man va gagner!"

Rajkaran Mishra
la source
5

# Exemples de processus

p = Proc.new { |x| puts x*2 }
[1,2,3].each(&p)              # The '&' tells ruby to turn the proc into a block 

proc = Proc.new { puts "Hello World" }
proc.call

# Exemples Lambda

lam = lambda { |x| puts x*2 }
[1,2,3].each(&lam)

lam = lambda { puts "Hello World" }
lam.call           

Différences entre Procs et Lambdas

Avant d'entrer dans les différences entre procs et lambdas, il est important de mentionner qu'ils sont tous les deux des objets Proc.

proc = Proc.new { puts "Hello world" }
lam = lambda { puts "Hello World" }

proc.class # returns 'Proc'
lam.class  # returns 'Proc'

Cependant, les lambdas sont une «saveur» différente des procs. Cette légère différence apparaît lors du retour des objets.

proc   # returns '#<Proc:0x007f96b1032d30@(irb):75>'
lam    # returns '<Proc:0x007f96b1b41938@(irb):76 (lambda)>'

1. Lambdas vérifie le nombre d'arguments, alors que procs ne le fait pas

lam = lambda { |x| puts x }    # creates a lambda that takes 1 argument
lam.call(2)                    # prints out 2
lam.call                       # ArgumentError: wrong number of arguments (0 for 1)
lam.call(1,2,3)                # ArgumentError: wrong number of arguments (3 for 1)

En revanche, les procs ne se soucient pas de savoir s'ils reçoivent un nombre incorrect d'arguments.

proc = Proc.new { |x| puts x } # creates a proc that takes 1 argument
proc.call(2)                   # prints out 2
proc.call                      # returns nil
proc.call(1,2,3)               # prints out 1 and forgets about the extra arguments

2. Lambdas et procs traitent le mot-clé "return" différemment

'return' à l'intérieur d'un lambda déclenche le code juste à l'extérieur du code lambda

def lambda_test
  lam = lambda { return }
  lam.call
  puts "Hello world"
end

lambda_test                 # calling lambda_test prints 'Hello World'

'return' à l'intérieur d'un proc déclenche le code en dehors de la méthode où le proc est exécuté

def proc_test
  proc = Proc.new { return }
  proc.call
  puts "Hello world"
end

proc_test                 # calling proc_test prints nothing

Et pour répondre à votre autre question, laquelle utiliser et quand? Je suivrai @jtbandes comme il l'a mentionné

Donc, pour la plupart des utilisations rapides, ils sont les mêmes, mais si vous voulez une vérification automatique des arguments stricts (qui peut aussi parfois aider au débogage), ou si vous devez utiliser l'instruction return pour renvoyer la valeur du proc, utilisez lambda.

Publié à l'origine ici

Arvind Singh
la source
1

De manière générale, les lambdas sont plus intuitives que les procs car elles sont plus similaires aux méthodes. Ils sont assez stricts sur l'arité et ils sortent simplement lorsque vous appelez le retour. Pour cette raison, de nombreux Rubyistes utilisent les lambdas comme premier choix, à moins qu'ils n'aient besoin des fonctionnalités spécifiques de procs.

Procs: objets de classe Proc. Comme les blocs, ils sont évalués dans la portée où ils sont définis. Lambdas: également des objets de classe Procmais subtilement différents des procs normaux. Ce sont des fermetures comme des blocs et des processus, et en tant que telles, elles sont évaluées dans la portée où elles sont définies.

Création de Proc

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

Création de lambda

b = lambda { |x| x 2 }

spirito_libero
la source
a = proc { |x| x 2 }est le même quea = Proc.new { |x| x 2 }
lacostenycoder
1

Voici une autre façon de comprendre cela.

Un bloc est un morceau de code attaché à l'appel à un appel d'une méthode sur un objet. Dans l'exemple ci-dessous, self est une instance d'une classe anonyme héritant d'ActionView :: Base dans le framework Rails (qui lui-même comprend de nombreux modules d'assistance). la carte est une méthode que nous appelons nous-mêmes. Nous passons un argument à la méthode puis nous attachons toujours le bloc à la fin de l'appel de la méthode:

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

Ok, donc nous passons un morceau de code à une méthode. Mais comment utiliser ce bloc? Une option consiste à convertir le morceau de code en un objet. Ruby propose trois façons de convertir un morceau de code en objet

# 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 un objet et stocke cet objet dans le bloc de variables locales. 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 passez un bloc à une méthode et que vous le convertissez à l'aide de &, l'objet qu'il crée utilise Proc.new pour effectuer la conversion.

Notez que j'ai évité d'utiliser "proc" comme option. C'est parce que c'est Ruby 1.8, c'est la même chose 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ètres, lambda se comporte comme un appel de méthode. Cela lèvera une exception si vous passez le mauvais nombre d'arguments. En revanche, Proc.new se comporte comme une affectation parallèle. Tous les arguments inutilisés sont convertis en nil:

> l = lambda {|a,b| puts "#{a} + #{b}" }
 => #<Proc:0x007fbffcb47e40@(irb):19 (lambda)> 
> l.call(1)
ArgumentError: wrong number of arguments (1 for 2)

> l2 = Proc.new {|a,b| puts "#{a} + #{b}" }
=> #<Proc:0x007fbffcb261a0@(irb):21> 
> l2.call(1)
1 + 

Deuxièmement, lambda et Proc.new gèrent le mot clé return différemment. Lorsque vous effectuez un retour à l'intérieur de Proc.new, il retourne en fait à partir de la méthode englobante, c'est-à-dire du contexte environnant. Lorsque vous revenez d'un bloc lambda, il revient simplement du bloc, pas de la méthode englobante. Fondamentalement, il quitte l'appel au bloc et continue l'exécution avec le reste 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 qu'avec Proc.new, nous pouvons utiliser des itérateurs dans le contexte des méthodes englobantes et 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 return à l'intérieur de l'itérateur, il revienne de la méthode englobante. N'oubliez pas que les blocs passés aux itérateurs sont convertis en objets à l'aide de Proc.new et c'est pourquoi lorsque nous utilisons return, il quittera la méthode englobante.

Vous pouvez considérer les lambdas comme des méthodes anonymes, elles isolent des blocs de code individuels dans un objet qui peut être traité comme une méthode. En fin de compte, pensez qu'un lambda se comporte comme une méthode anomyous et que Proc.new se comporte comme du code en ligne.

Donato
la source
0

Un article utile sur les guides rubis: blocs, procs et lambdas

Les processus retournent à partir de la méthode actuelle, tandis que les lambdas reviennent à partir du lambda lui-même.

Les processus ne se soucient pas du nombre correct d'arguments, tandis que lambdas lèvera une exception.

Sindhu Shree
la source
-3

la différence entre proc et lambda est que proc est juste une copie de code avec des arguments remplacés à son tour, tandis que lambda est une fonction comme dans d'autres langages. (comportement de retour, vérification des arguments)

Nighon
la source