"Para" vs "cada" en Ruby

200

Acabo de tener una pregunta rápida sobre los bucles en Ruby. ¿Hay alguna diferencia entre estas dos formas de iterar a través de una colección?

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

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

Solo me pregunto si estos son exactamente iguales o si tal vez hay una sutil diferencia (posiblemente cuando @collectiones nula).

mportiz08
fuente

Respuestas:

315

Esta es la única diferencia:

cada:

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

para:

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

Con el forbucle, la variable iteradora sigue viva después de que se realiza el bloque. Con el eachbucle, no lo hace, a menos que ya se haya definido como una variable local antes de que se inicie el bucle.

Aparte de eso, fores solo azúcar de sintaxis para el eachmétodo.

Cuando @collectiones nilambos bucles lanzan una excepción:

Excepción: variable local indefinida o método `@collection 'para main: Object

Jeremy Ruten
fuente
3
¿hay una buena razón por la cual x permanece en el caso o es este mal diseño: P? Me parece que esto es bastante poco intuitivo en comparación con la mayoría de los otros idiomas.
cyc115
3
@ cyc115 La razón por la que xpermanece en el escenario for se debe al hecho de que las palabras clave (en general) no crean nuevos ámbitos. Si , a menos , comenzar , para , mientras , etc. todo el trabajo con el ámbito actual. #eachSin embargo acepta un bloqueo. Los bloques siempre agregan su propio alcance sobre el alcance actual. Lo que significa que declarar una nueva variable en el bloque (por lo tanto, un nuevo alcance) no será accesible desde fuera del bloque ya que ese alcance adicional no está disponible allí.
3limin4t0r
30

Tu primer ejemplo

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

Es más idiomático . Si bien Ruby admite construcciones en bucle como fory while, generalmente se prefiere la sintaxis de bloque.

Otra diferencia sutil es que cualquier variable que declare dentro de un forbucle estará disponible fuera del bucle, mientras que aquellas dentro de un bloque iterador son efectivamente privadas.

Bayard Randel
fuente
whiley en untilrealidad tienen algunos usos muy concretos que no se pueden reemplazar con cada uno, como por ejemplo generar valores únicos o para REPL.
máximo
2

Parece que no hay diferencia, forutiliza eachdebajo.

$ 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

Como dice Bayard, cada uno es más idiomático. Se esconde más de ti y no requiere características especiales de idioma. Por el comentario de Telémaco

for .. in .. establece el iterador fuera del alcance del bucle, por lo que

for a in [1,2]
  puts a
end

hojas adefinidas una vez finalizado el ciclo. Donde como eachno. Cuál es otra razón a favor del uso each, porque la variable temporal vive un período más corto.

BarrocoBobcat
fuente
1
No es una diferencia pequeña (como yjerem, ChristopheD y Bayard mención) en relación con el alcance variable.
Telémaco el
Incorrecto, forno se usa eachdebajo. Ver otras respuestas.
akuhn
@akuhn Para obtener más aclaraciones, consulte esta pregunta y sus excelentes respuestas.
Sagar Pandya
2

Nunca usarlo forpuede causar errores casi imposibles de rastrear.

No se deje engañar, no se trata de código idiomático o problemas de estilo. La implementación de Ruby fortiene una falla grave y no debe usarse.

Aquí hay un ejemplo donde se forintroduce un error,

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

Huellas dactilares

quz
quz
quz

Usando %w{foo bar quz}.each { |n| ... }impresiones

foo
bar
quz

¿Por qué?

En un forbucle, la variable nse define una sola vez y luego se usa esa definición para todas las iteraciones. Por lo tanto, cada bloque se refiere al mismo nque tiene un valor quzpara cuando finaliza el ciclo. ¡Insecto!

En un eachbucle, nse define una variable nueva para cada iteración, por ejemplo, arriba, la variable nse define tres veces por separado. Por lo tanto, cada bloque se refiere a un separado ncon los valores correctos.

akuhn
fuente
0

Hasta donde sé, usar bloques en lugar de estructuras de control en lenguaje es más idiomático.

Jonathan Sterling
fuente
0

Solo quiero hacer un punto específico sobre el ciclo for in en Ruby. Puede parecer una construcción similar a otros lenguajes, pero de hecho es una expresión como cualquier otra construcción en bucle en Ruby. De hecho, for in funciona con objetos Enumerable al igual que cada iterador.

La colección pasada a for in puede ser cualquier objeto que tenga un método de cada iterador. Las matrices y los hash definen cada método, y muchos otros objetos Ruby también lo hacen. El bucle for / in llama a cada método del objeto especificado. A medida que ese iterador produce valores, el bucle for asigna cada valor (o cada conjunto de valores) a la variable (o variables) especificadas y luego ejecuta el código en el cuerpo.

Este es un ejemplo tonto, pero ilustra el punto de que el bucle for in funciona con CUALQUIER objeto que tenga cada método, tal como lo hace cada iterador:

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

Y ahora el cada iterador:

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

Como puede ver, ambos están respondiendo a cada método que devuelve valores al bloque. Como todos declararon aquí, definitivamente es preferible usar cada iterador sobre el ciclo for in. Solo quería llevar a casa el punto de que no hay nada mágico en el bucle for. Es una expresión que invoca cada método de una colección y luego lo pasa a su bloque de código. Por lo tanto, es un caso muy raro que necesitaría usar. Use cada iterador casi siempre (con el beneficio adicional del alcance del bloque).

Donato
fuente
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

En el ciclo 'for', la variable local sigue viva después de cada ciclo. En 'cada' bucle, la variable local se actualiza después de cada bucle.

Kelvin Tan
fuente