2010-05-19 17 views
11

¿Por qué crearía una referencia de proxy para un objeto en Ruby, utilizando el método to_enum en lugar de solo usar el objeto directamente? No puedo pensar en ningún uso práctico para esto, tratando de entender este concepto & donde alguien podría usarlo, pero todos los ejemplos que he visto parecen muy triviales.¿Cuál es la ventaja de crear un objeto enumerable usando to_enum en Ruby?

Por ejemplo, ¿por qué usar:

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

en lugar de

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

Sé que esto es un ejemplo muy sencillo, ¿alguien tiene alguna ejemplos del mundo real?

Respuesta

1

Esta no es toda una respuesta a su pregunta, pero espero que sea relevante.

En el segundo ejemplo, llama al each_char sin pasar un bloque. Cuando se llama sin un bloque each_char devuelve un Enumerator por lo que sus ejemplos son en realidad solo dos formas de hacer lo mismo. (Es decir, tanto como resultado la creación de un objeto enumerable.)

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

más incorporado métodos que aceptan un bloque devolverá un empadronador en caso no se proporciona ningún bloque (como String#each_char en su ejemplo). Para estos, no hay ninguna razón para usar to_enum; ambos tendrán el mismo efecto.

Sin embargo, algunos métodos no devuelven un Enumerador. En esos casos, es posible que necesite usar 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 

Como otro ejemplo, Array#product, #uniq y #uniq! no utilizaron para aceptar un bloque. En 1.9.2, esto se cambió, pero para mantener la compatibilidad, los formularios sin un bloque no pueden devolver un Enumerator. Uno puede todavía "manualmente" uso to_enum para obtener un enumerador:

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 

El uso principal de to_enum es cuando se está implementando un método iterativo. Normalmente tendrá como primera línea:

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

También es útil si usted está utilizando bibliotecas de terceros que don' devolver un enumerador. –

2

Creo que tiene algo que ver con los iteradores internos y externos. Cuando devuelve un enumerador como este:

p = "hello".enum_for(:each_char) 

p es un enumerador externo. Una ventaja de los iteradores externos es que:

Los iteradores externos son más flexibles que los iteradores internos. Es fácil comparar dos colecciones para la igualdad con un iterador externo, por ejemplo, pero es prácticamente imposible con iteradores internos ... Pero, por otro lado, los iteradores internos son más fáciles de usar, ya que definen la lógica de iteración para usted. [Del libro The Ruby Programming Language, cap. 5.3]

Entonces, con el iterador externo que puede hacer, p.:

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

Digamos que queremos tomar una serie de llaves y una matriz de valores y coserlas en un Hash:

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

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

Es mucho más fácil leer el primer método, ¿no crees? No es mucho más fácil, pero imagina si de alguna manera manipulamos elementos de 3 arreglos. 5? 10?

+1

Ejemplo terrible. Usar 'next' matará tu rendimiento. Ambos ejemplos no manejan las diferencias de tamaño de la misma manera (uno aumenta, el otro se detiene). –

0

Es ideal para objetos generadores grandes o infinitos. P. ej., Lo siguiente le dará un enumerador para toda la secuencia de Fibonacci, de 0 a infinito.

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 permite efectivamente a escribir esto con regularidad yields sin tener que meterse con Fiber s.

A continuación, puede se mire como se desea, y va a ser muy eficiente de la memoria, ya que no hay matrices serán almacenados en la memoria:

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] 
Cuestiones relacionadas