В чем преимущество создания перечислимого объекта с использованием to_enum в Ruby? - PullRequest
14 голосов
/ 19 мая 2010

Зачем вам создавать прокси-ссылку на объект в Ruby, используя метод to_enum, а не просто используя объект напрямую? Я не могу придумать какого-либо практического использования для этого, пытаясь понять эту концепцию и где кто-то может ее использовать, но все примеры, которые я видел, кажутся очень тривиальными.

Например, зачем использовать:

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

вместо

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

Я знаю, что это очень простой пример. У кого-нибудь есть примеры из реального мира?

Ответы [ 5 ]

14 голосов
/ 19 мая 2010

Большинство встроенных методов, которые принимают блок, возвращают перечислитель, если блок не предоставлен (например, String#each_char в вашем примере). Для этого нет причин использовать to_enum; оба будут иметь одинаковый эффект.

Однако некоторые методы не возвращают Enumerator. В этих случаях вам может понадобиться использовать 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

В качестве другого примера, Array#product, #uniq и #uniq! не использовали для принятия блока. В 1.9.2 это было изменено, но для обеспечения совместимости формы без блока не могут возвращать Enumerator. Еще можно «вручную» использовать to_enum, чтобы получить перечислитель:

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 

Основное использование to_enum - это когда вы реализуете свой собственный итерационный метод. Обычно вы будете иметь в качестве первой строки:

def my_each
  return to_enum :my_each unless block_given?
  # ...
end
3 голосов
/ 17 марта 2012

Допустим, мы хотим взять массив ключей и массив значений и зашить их в хэш:

с # 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

Без #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

Гораздо проще читать первый метод, не правда ли?Не намного проще, но представьте, если мы каким-то образом манипулируем элементами из 3-х массивов?5?10

3 голосов
/ 20 мая 2010

Я думаю, что это как-то связано с внутренними и внешними итераторами. Когда вы возвращаете перечислитель, как это:

p = "hello".enum_for(:each_char)

p - внешний перечислитель. Одним из преимуществ внешних итераторов является то, что:

Внешние итераторы более гибкие, чем внутренние итераторы. Например, легко сравнить две коллекции на равенство с внешним итератором, но практически невозможно с внутренними итераторами…. Но с другой стороны, внутренние итераторы проще в использовании, потому что они определяют логику итерации для вас. [Из книги по языку программирования Ruby, гл. 5,3]

Итак, с внешним итератором вы можете сделать, например ::

p = "hello".enum_for(:each_char)
loop do
    puts p.next
end
1 голос
/ 21 февраля 2015

Отлично подходит для больших или бесконечных объектов-генераторов. Например, следующее даст вам счетчик для всей последовательности Фибоначчи, от 0 до бесконечности.

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 позволяет эффективно писать это с обычным yields без необходимости связываться с Fiber s.

Затем вы можете нарезать его по своему усмотрению, и это будет очень эффективно использовать память, поскольку в памяти не будет храниться никаких массивов:

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]
1 голос
/ 19 мая 2010

Это не совсем ответ на ваш вопрос, но, надеюсь, он уместен.

Во втором примере вы звоните each_char без прохождения блока. При вызове без блока each_char возвращает Enumerator, поэтому ваши примеры - это всего лишь два способа сделать то же самое. (т.е. оба результата приводят к созданию перечислимого объекта.)

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"]
...