„Za” vs „każdy” w Ruby

200

Właśnie miałem szybkie pytanie dotyczące pętli w Ruby. Czy istnieje różnica między tymi dwoma sposobami iteracji w kolekcji?

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

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

Zastanawiam się tylko, czy są dokładnie takie same, czy może istnieje subtelna różnica (być może, gdy @collectionzero).

mportiz08
źródło

Odpowiedzi:

315

To jedyna różnica:

każdy:

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

dla:

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

W przypadku forpętli zmienna iteratora nadal żyje po zakończeniu bloku. W przypadku eachpętli tak nie jest, chyba że przed rozpoczęciem pętli została zdefiniowana jako zmienna lokalna.

Poza tym forjest tylko cukier składniowy dla tej eachmetody.

Gdy @collectionto nilobie pętle rzut wyjątek:

Wyjątek: niezdefiniowana zmienna lokalna lub metoda `@collection 'dla main: Object

Jeremy Ruten
źródło
3
czy istnieje dobry powód, dla którego x pozostaje w przypadku, czy też jest to zły projekt: P? Wydaje mi się, że jest to raczej nieintuicyjne porównanie z większością innych języków.
cyc115
3
@ cyc115 Powodem, dla którego xpozostaje scenariusz for , jest fakt, że (ogólnie rzecz biorąc) słowa kluczowe nie tworzą nowych zakresów. jeśli , chyba , zacząć , za , natomiast itp wszelkich prac z bieżącego zakresu. #eachakceptuje jednak blok. Bloki zawsze dodają własny zakres na bieżącym zakresie. Oznacza to, że zadeklarowanie nowej zmiennej w bloku (a więc nowego zakresu) nie będzie dostępne spoza bloku, ponieważ ten dodatkowy zakres nie jest tam dostępny.
Elimin4t0r
43

Dobre wyjaśnienie znajduje się w „ The Evils of the Loop ” (istnieje jedna niewielka różnica w zakresie określania zakresu zmiennych).

Używanie eachjest uważane za bardziej idiomatyczne użycie Ruby.

ChristopheD
źródło
@zachlatta: Dziękujemy za powiadomienie. Zedytuję link, aby wskazać wariant artykułu webarchive.org!
ChristopheD
1
graysoftinc.com/early-steps/the-evils-of-the-for-loop to nowy link, teraz, gdy strona JEG2 jest ponownie online.
pnomolos
30

Twój pierwszy przykład

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

jest bardziej idiomatyczny . Podczas gdy Ruby obsługuje konstrukcje zapętlające się jak fori while, ogólnie preferowana jest składnia bloku.

Inną subtelną różnicą jest to, że dowolna zmienna zadeklarowana w forpętli będzie dostępna poza pętlą, podczas gdy te w bloku iteratora są faktycznie prywatne.

Bayard Randel
źródło
whilei untilfaktycznie mają bardzo konkretne zastosowania, których nie można zastąpić każdym z nich, na przykład generowanie unikalnych wartości lub REPL.
maks.
6

Jeszcze jeden inny ...

number = ["one", "two", "three"]
 => ["one", "two", "three"] 

loop1 = []
loop2 = []

number.each do |c|
  loop1 << Proc.new { puts c }
end
 => ["one", "two", "three"] 

for c in number
  loop2 << Proc.new { puts c }
end
 => ["one", "two", "three"] 

loop1[1].call
two
 => nil 

loop2[1].call
three
 => nil 

źródło: http://paulphilippov.com/articles/enumerable-each-vs-for-loops-in-ruby

dla lepszego wyjaśnienia: http://www.ruby-forum.com/topic/179264#784884

Pan Black
źródło
2

Wygląda na to, że nie ma różnicy, forużywa eachpod spodem.

$ 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

Jak mówi Bayard, każdy jest bardziej idiomatyczny. Ukrywa przed tobą więcej i nie wymaga specjalnych funkcji językowych. Komentarz Per Telemachusa

for .. in .. ustawia iterator poza zakresem pętli, więc

for a in [1,2]
  puts a
end

liście azdefiniowane po zakończeniu pętli. Gdzie eachnie. To kolejny powód do używania each, ponieważ zmienna temp żyje krócej.

BaroqueBobcat
źródło
1
Jest to niewielka różnica (jak yjerem, ChristopheD i Bayard wzmianki) dotyczące zmiennego zakresu.
Telemachus
Niepoprawnie, fornie używa się eachpod spodem. Zobacz inne odpowiedzi.
akuhn
@akuhn Aby uzyskać dodatkowe wyjaśnienia, zobacz to pytanie i obie doskonałe odpowiedzi.
Sagar Pandya
2

Nigdy nie używaj forgo, może powodować prawie niewykrywalne błędy.

Nie daj się zwieść, nie chodzi o idiomatyczny kod lub problemy ze stylem. Implementacja Ruby forma poważną wadę i nie powinna być używana.

Oto przykład, w którym forwprowadza błąd,

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

Wydruki

quz
quz
quz

Korzystanie z %w{foo bar quz}.each { |n| ... }wydruków

foo
bar
quz

Czemu?

W forpętli zmienna njest definiowana tylko raz, a następnie dla wszystkich iteracji używana jest jedna definicja. Stąd każdy blok odnosi się do tego samego, nktóry ma wartość quzdo czasu zakończenia pętli. Pluskwa!

W eachpętli świeża zmienna njest definiowana dla każdej iteracji, na przykład powyżej zmiennej njest definiowana trzy osobno. Dlatego każdy blok odnosi się do osobnego nz poprawnymi wartościami.

akuhn
źródło
0

O ile mi wiadomo, stosowanie bloków zamiast struktur kontrolnych w języku jest bardziej idiomatyczne.

Jonathan Sterling
źródło
0

Chcę tylko zwrócić uwagę na pętlę for in w Ruby. Może wydawać się konstrukcją podobną do innych języków, ale w rzeczywistości jest to wyrażenie jak każda inna konstrukcja pętli w Rubim. W rzeczywistości for in działa z obiektami policzalnymi tak jak każdy iterator.

Kolekcja przekazywana do w może być dowolnym obiektem, który ma metodę każdego iteratora. Tablice i skróty definiują każdą metodę, podobnie jak wiele innych obiektów Ruby. Pętla for / in wywołuje każdą metodę określonego obiektu. Ponieważ iterator zwraca wartości, pętla for przypisuje każdą wartość (lub każdy zestaw wartości) do określonej zmiennej (lub zmiennych), a następnie wykonuje kod w treści.

To głupi przykład, ale ilustruje to, że pętla for in działa z KAŻDYM obiektem, który ma każdą metodę, podobnie jak robi to każdy iterator:

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

A teraz każdy iterator:

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

Jak widać, oba reagują na każdą metodę, która zwraca wartości z powrotem do bloku. Jak wszyscy tutaj stwierdzili, zdecydowanie lepiej jest używać każdego iteratora niż w pętli for. Chciałem po prostu jechać do domu do tego stopnia, że ​​w pętli for in nie ma nic magicznego. Jest to wyrażenie, które wywołuje każdą metodę kolekcji, a następnie przekazuje ją do swojego bloku kodu. W związku z tym jest to bardzo rzadki przypadek, w którym należałoby użyć w. Używaj każdego iteratora prawie zawsze (z dodatkową zaletą zakresu bloków).

Donato
źródło
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

W pętli „for” zmienna lokalna nadal żyje po każdej pętli. W pętli „każda” zmienna lokalna odświeża się po każdej pętli.

Kelvin Tan
źródło