«Pour» vs «chacun» en Ruby


200

Je viens d'avoir une petite question concernant les boucles en Ruby. Y a-t-il une différence entre ces deux façons d'itérer à travers une collection?

# way 1
@collection.each do |item|
  # do whatever
end

# way 2
for item in @collection
  # do whatever
end

Je me demande simplement si ce sont exactement les mêmes ou s'il y a peut-être une différence subtile (peut-être quand @collectionest-elle nulle).

Réponses:


315

C'est la seule différence:

chaque:

irb> [1,2,3].each { |x| }
  => [1, 2, 3]
irb> x
NameError: undefined local variable or method `x' for main:Object
    from (irb):2
    from :0

pour:

irb> for x in [1,2,3]; end
  => [1, 2, 3]
irb> x
  => 3

Avec la forboucle, la variable itérateur vit toujours une fois le bloc terminé. Avec leeach boucle, ce n'est pas le cas, sauf si elle a déjà été définie comme une variable locale avant le démarrage de la boucle.

Autre que cela, forc'est juste du sucre de syntaxe pour la eachméthode.

Quand @collectionest les nildeux boucles jettent une exception:

Exception: variable locale non définie ou méthode `@collection 'pour main: Object


3
y a-t-il une bonne raison pour laquelle x reste dans le cas pour ou est-ce une mauvaise conception: P? Il me semble que cela n'est pas intuitif par rapport à la plupart des autres langues.
cyc115

3
@ cyc115 La raison pour laquelle xreste dans le scénario for est due au fait que (généralement) les mots clés ne créent pas de nouvelles étendues. si , à moins que , commence , pour , tandis que , etc., tous fonctionnent avec la portée actuelle. #eachaccepte cependant un bloc. Les blocs ajoutent toujours leur propre étendue au-dessus de l'étendue actuelle. Cela signifie que déclarer une nouvelle variable dans le bloc (donc une nouvelle étendue) ne sera pas accessible de l'extérieur du bloc car cette étendue supplémentaire n'y est pas disponible.
3limin4t0r

43

Voir " The Evils of the For Loop " pour une bonne explication (il y a une petite différence compte tenu de la portée des variables).

L'utilisation eachest considérée comme une utilisation plus idiomatique de Ruby.


@zachlatta: Merci d'avoir informé. Je vais modifier le lien pour pointer vers une variante webarchive.org de l'article!
ChristopheD

1
graysoftinc.com/early-steps/the-evils-of-the-for-loop est le nouveau lien, maintenant que le site de JEG2 est de retour en ligne.
pnomolos

30

Votre premier exemple,

@collection.each do |item|
  # do whatever
end

est plus idiomatique . Alors que Ruby prend en charge les constructions en boucle comme foret while, la syntaxe des blocs est généralement préférée.

Une autre différence subtile est que toute variable que vous déclarez dans une forboucle sera disponible en dehors de la boucle, tandis que celles dans un bloc itérateur sont effectivement privées.


whileet untilont en fait des utilisations très concrètes qui ne peuvent pas être remplacées par chacune, comme par exemple la génération de valeurs uniques ou pour les REPL.
maximum


2

Il semble qu'il n'y ait pas de différence, forutilise en eachdessous.

$ irb
>> for x in nil
>> puts x
>> end
NoMethodError: undefined method `each' for nil:NilClass
    from (irb):1
>> nil.each {|x| puts x}
NoMethodError: undefined method `each' for nil:NilClass
    from (irb):4

Comme Bayard le dit, chacun est plus idiomatique. Il vous cache davantage et ne nécessite pas de fonctionnalités linguistiques spéciales. Selon le commentaire de Télémaque

for .. in .. place l'itérateur en dehors de la portée de la boucle, donc

for a in [1,2]
  puts a
end

feuilles adéfinies une fois la boucle terminée. Où eachpas. C'est une autre raison en faveur de l'utilisation each, car la variable temp vit une période plus courte.


1
Il y a une petite différence (comme yjerem, ChristopheD et Bayard le mentionnent) concernant la portée variable.
Télémaque le

Incorrect, forn'utilise pas en eachdessous. Voir d'autres réponses.
akuhn

@akuhn Pour plus de précisions, veuillez consulter cette question et ses excellentes réponses.
Sagar Pandya

2

Ne jamais l'utiliser forpeut provoquer des bugs presque introuvables.

Ne vous laissez pas berner, il ne s'agit pas de problèmes de code ou de style idiomatiques. L'implémentation de Ruby fora un sérieux défaut et ne doit pas être utilisée.

Voici un exemple où forintroduit un bug,

class Library
  def initialize
    @ary = []
  end
  def method_with_block(&block)
    @ary << block
  end
  def method_that_uses_these_blocks
    @ary.map(&:call)
  end
end

lib = Library.new

for n in %w{foo bar quz}
  lib.method_with_block { n }
end

puts lib.method_that_uses_these_blocks

Impressions

quz
quz
quz

Utilisation d' %w{foo bar quz}.each { |n| ... }impressions

foo
bar
quz

Pourquoi?

Dans une forboucle, la variable nest définie une seule fois, puis cette définition est utilisée pour toutes les itérations. Par conséquent, chaque bloc fait référence au même nqui a une valeur quzau moment où la boucle se termine. Punaise!

Dans une eachboucle, une nouvelle variable nest définie pour chaque itération, par exemple au-dessus de la variable nest définie trois fois distinctes. Par conséquent, chaque bloc fait référence à un élément distinct navec les valeurs correctes.


0

Pour autant que je sache, l'utilisation de blocs au lieu de structures de contrôle en langage est plus idiomatique.


0

Je veux juste faire un point spécifique sur la boucle for in dans Ruby. Cela peut sembler une construction similaire à d'autres langages, mais en fait c'est une expression comme toutes les autres constructions en boucle de Ruby. En fait, le for in fonctionne avec des objets Enumerable tout comme chaque itérateur.

La collection passée à for in peut être n'importe quel objet qui a une méthode itérateur pour chaque. Les tableaux et les hachages définissent chaque méthode, et de nombreux autres objets Ruby le font également. La boucle for / in appelle la méthode each de l'objet spécifié. Lorsque cet itérateur fournit des valeurs, la boucle for affecte chaque valeur (ou chaque ensemble de valeurs) à la variable (ou aux variables) spécifiée (s), puis exécute le code dans body.

C'est un exemple stupide, mais illustre le point que la boucle for in fonctionne avec N'IMPORTE QUEL objet qui a une méthode each, tout comme la façon dont chaque itérateur fait:

class Apple
  TYPES = %w(red green yellow)
  def each
    yield TYPES.pop until TYPES.empty?
  end
end

a = Apple.new
for i in a do
  puts i
end
yellow
green
red
=> nil

Et maintenant, chaque itérateur:

a = Apple.new
a.each do |i|
  puts i
end
yellow
green
red
=> nil

Comme vous pouvez le voir, les deux répondent à chaque méthode qui renvoie des valeurs au bloc. Comme tout le monde l'a dit ici, il est certainement préférable d'utiliser chaque itérateur sur la boucle for in. Je voulais juste ramener à la maison le point qu'il n'y a rien de magique dans la boucle for in. Il s'agit d'une expression qui appelle chaque méthode d'une collection, puis la transmet à son bloc de code. Par conséquent, c'est un cas très rare que vous auriez besoin d'utiliser pour dans. Utilisez chaque itérateur presque toujours (avec l'avantage supplémentaire de l'étendue du bloc).


0
(1..4).each { |i| 


  a = 9 if i==3

  puts a 


}
#nil
#nil
#9
#nil

for i in 1..4

  a = 9 if i==3

  puts a

end
#nil
#nil
#9
#9

Dans la boucle 'for', la variable locale est toujours en vie après chaque boucle. Dans «chaque» boucle, la variable locale est actualisée après chaque boucle.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.