¿Cuál es la mejor manera de devolver un Enumerator :: Lazy cuando su clase no define #each?

Enumerable#lazy basa en su enumerable proporcionando un método #each . Si su enumerable no tiene un método #each , no puede usar #lazy . Ahora Kernel#enum_for y #to_enum proporcionan la flexibilidad para especificar un método de enumeración distinto de #each :

 Kernel#enum_for(method = :each, *args) 

Pero #enum_for y sus amigos siempre construyen enumeradores simples (no perezosos), nunca Enumerator::Lazy .

Veo que Enumerator en Ruby 1.9.3 ofrece esta forma similar de #new:

 Enumerator#new(obj, method = :each, *args) 

Desafortunadamente ese constructor ha sido completamente eliminado en Ruby 2.0. Además, creo que nunca estuvo disponible en absoluto en Enumerator::Lazy . Entonces, me parece que si tengo una clase con un método para el que quiero devolver un enumerador vago, si esa clase no tiene #each entonces tengo que definir alguna clase auxiliar que sí define #each .

Por ejemplo, tengo una clase de Calendar . Realmente no tiene sentido que ofrezca enumerar todas las fechas desde el principio de todos los tiempos. Un #each sería inútil. En cambio, ofrezco un método que enumera (perezosamente) a partir de una fecha de inicio:

  class Calendar ... def each_from(first) if block_given? loop do yield first if include?(first) first += step end else EachFrom.new(self, first).lazy end end end 

Y esa clase de EachFrom ve así:

 class EachFrom include Enumerable def initialize(cal, first) @cal = cal @first = first end def each @cal.each_from(@first) do |yielder, *vals| yield yielder, *vals end end end 

Funciona pero se siente pesado. Tal vez debería subclasificar Enumerator::Lazy y definir un constructor como el que está en desuso de Enumerator . ¿Qué piensas?

Creo que deberías devolver un Enumerator normal usando to_enum :

 class Calendar # ... def each_from(first) return to_enum(:each_from, first) unless block_given? loop do yield first if include?(first) first += step end end end 

Esto es lo que la mayoría de los Rubyistas esperarían. Aunque es un Enumerable infinito, todavía es utilizable, por ejemplo:

 Calendar.new.each_from(1.year.from_now).first(10) # => [...first ten dates...] 

Si realmente necesitan un enumerador perezoso, pueden llamar lazy sí mismos:

 Calendar.new.each_from(1.year.from_now) .lazy .map{...} .take_while{...} 

Si realmente desea devolver un enumerador perezoso, puede llamar lazy desde su método:

  # ... def each_from(first) return to_enum(:each_from, first).lazy unless block_given? #... 

Sin embargo, no lo recomendaría, ya que sería inesperado (IMO), podría ser una exageración y tendrá menos rendimiento.

Finalmente, hay un par de ideas erróneas en tu pregunta:

  • Todos los métodos de Enumerable asumen each , no solo lazy .

  • Puede definir each método que requiera un parámetro si lo desea e incluir Enumerable . La mayoría de los métodos de Enumerable no funcionarán, pero each_with_index y un par de otros enviarán los argumentos para que estos se puedan utilizar de inmediato.

  • El Enumerator.new sin un bloque se ha ido porque to_enum es lo que se debe usar. Tenga en cuenta que la forma de bloque permanece. También hay un constructor para Lazy , pero está destinado a partir de un Enumerable existente.

  • to_enum que to_enum nunca crea un enumerador perezoso, pero eso no es del todo cierto. Enumerator::Lazy#to_enum está especializado en devolver un enumerador perezoso. Cualquier método de usuario en Enumerable que llame a to_enum mantendrá un enumerador perezoso perezoso.