2010-05-19 10 views
11

Po co tworzyć referencje proxy do obiektu w Ruby, używając metody to_enum, a nie tylko bezpośrednio z obiektu? Nie mogę wymyślić żadnego praktycznego zastosowania tego, próbując zrozumieć tę koncepcję, aby ktoś mógł z niej skorzystać, ale wszystkie przykłady, które widziałem, wydają się bardzo banalne.Jaka jest zaleta tworzenia przeliczalnego obiektu przy użyciu to_enum w Ruby?

Na przykład, dlaczego używać:

"hello".enum_for(:each_char).map {|c| c.succ } 

zamiast

"hello".each_char.map {|c| c.succ } 

wiem, że jest to bardzo prosty przykład, czy ktoś ma jakieś przykłady świata rzeczywistego?

Odpowiedz

1

To nie jest odpowiedź na twoje pytanie, ale mam nadzieję, że jest to istotne.

W twoim drugim przykładzie dzwonisz pod numer each_char bez przekazywania bloku. Po wywołaniu bez bloku each_char zwraca Enumerator, więc twoje przykłady to właściwie tylko dwa sposoby robienia tego samego. (Wynik to znaczy zarówno w sprawie utworzenia przeliczalnego obiektu.)

irb(main):016:0> e1 = "hello".enum_for(:each_char) 
=> #<Enumerator:0xe15ab8> 
irb(main):017:0> e2 = "hello".each_char 
=> #<Enumerator:0xe0bd38> 
irb(main):018:0> e1.map { |c| c.succ } 
=> ["i", "f", "m", "m", "p"] 
irb(main):019:0> e2.map { |c| c.succ } 
=> ["i", "f", "m", "m", "p"] 
12

Większość wbudowany w metodach, które akceptują blok zwróci wyliczający w przypadku bloku nie jest (jak String#each_char w przykładzie). W tym przypadku nie ma powodu, aby używać to_enum; oba będą miały taki sam efekt.

Kilka metod nie zwraca jednak modułu wyliczającego. W takim przypadku może być konieczne użycie to_enum.

# How many elements are equal to their position in the array? 
[4, 1, 2, 0].to_enum(:count).each_with_index{|elem, index| elem == index} #=> 2 

Jako inny przykład, Array#product, #uniq i #uniq! nie używać do przyjęcia bloku. W wersji 1.9.2 zmieniono to, ale aby zachować kompatybilność, formularze bez bloku nie mogą zwracać wartości Enumerator. Można jeszcze „ręcznie” należy użyć to_enum uzyskać wyliczający:

require 'backports/1.9.2/array/product' # or use Ruby 1.9.2+ 
# to avoid generating a huge intermediary array: 
e = many_moves.to_enum(:product, many_responses) 
e.any? do |move, response| 
    # some criteria 
end 

Głównym zastosowaniem to_enum jest, kiedy realizują własną metodę iteracyjną. Zazwyczaj będzie to pierwsza linia:

def my_each 
    return to_enum :my_each unless block_given? 
    # ... 
end 
+0

Jest to także przydatne, jeśli używasz bibliotek innych firm, które don” t zwraca moduł wyliczający. –

2

Myślę, że ma to coś wspólnego z Iteratorami wewnętrznymi i zewnętrznymi. Po zwróceniu modułu wyliczającego w następujący sposób:

p = "hello".enum_for(:each_char) 

p jest zewnętrznym modułem wyliczającym. Zaletą zewnętrznych iteratorów jest to, że:

Zewnętrzne iteratory są bardziej elastyczne niż wewnętrzne iteratory. Łatwo jest porównać dwie kolekcje pod kątem równości z zewnętrznym iteratorem, ale jest to praktycznie niemożliwe przy wewnętrznych iteratorach .... Ale z drugiej strony, iteratory wewnętrzne są łatwiejsze w użyciu, ponieważ definiują dla ciebie logikę iteracyjną. [Z książki The Ruby Programming Language, rozdz. 5.3]

Dzięki zewnętrznemu iteratorowi można to zrobić, np.:

p = "hello".enum_for(:each_char) 
loop do 
    puts p.next 
end 
2

Powiedzmy, że chcesz wziąć tablicę klawiszy i tablicę wartości i przyszyć je w Hash:

Z #to_enum

def hashify(k, v) 
    keys = k.to_enum(:each) 
    values = v.to_enum(:each) 
    hash = [] 
    loop do 
    hash[keys.next] = values.next 
    # No need to check for bounds, 
    # as #next will raise a StopIteration which breaks from the loop 
    end 
    hash 
end 

Bez # to_enum:

def hashify(k, v) 
    hash = [] 
    keys.each_with_index do |key, index| 
    break if index == values.length 
    hash[key] = values[index] 
    end 
    hash 
end 

O wiele łatwiej jest przeczytać pierwszą metodę, nie sądzisz? Nie tona łatwiej, ale wyobraź sobie, że gdybyśmy w jakiś sposób manipulowali przedmiotami z 3 tablic? 5? 10?

+1

Okropny przykład. Użycie 'next' zabije twoją wydajność. Oba przykłady nie radzą sobie z różnicami wielkości w taki sam sposób (jeden podnosi, a drugi zatrzymuje). –

0

Świetnie nadaje się do dużych lub nieskończonych obiektów generatora. Np. Poniżej podajesz moduł wyliczający dla wszystkich wartości Fibonacciego od 0 do nieskończoności.

def fib_sequence 
    return to_enum(:fib_sequence) unless block_given? 
    yield 0 
    yield 1 
    x,y, = 0, 1 
    loop { x,y = y,x+y; yield(y) } 
end 

to_enum skutecznie pozwala napisać to z regularnym yields bez bałaganu z Fiber s.

Następnie można pokroić go jak chcesz, i to będzie bardzo pamięć wydajny, ponieważ żadne tablice zostaną zapisane w pamięci:

module Slice 
    def slice(range) 
     return to_enum(:slice, range) unless block_given? 
     start, finish = range.first, range.max + 1 
     copy = self.dup 
     start.times { copy.next } 
     (finish-start).times { yield copy.next } 
    end 
end 
class Enumerator 
    include Slice 
end 

fib_sequence.slice(0..10).to_a 
#=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55] 
fib_sequence.slice(10..20).to_a                               
#=> [55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] 
Powiązane problemy