2008-09-21 7 views
11

¿Alguien ha encontrado una buena solución para las listas evaluadas perezosamente en Perl? He intentado varias formas de convertir algo así como¿Existe una solución de Perl para listas perezosa de este lado de Perl 6?

for my $item (map { ... } @list) { 
} 

en una evaluación perezosa - por @List tie-ción, por ejemplo. Estoy tratando de evitar romper y escribir un filtro de fuente para hacerlo, porque se meten con tu habilidad para depurar el código. ¿Alguien ha tenido éxito? ¿O simplemente tiene que descomponerse y usar un ciclo while?

Nota: Supongo que debo mencionar que estoy un poco enganchado a cadenas grep-map a veces largas para listas de transformación funcional. Entonces, no es tanto el ciclo foreach como el ciclo while. Es que las expresiones de mapa tienden a incluir más funcionalidades en el mismo espacio vertical.

Respuesta

13

Como se mencionó anteriormente, para (cada uno) es un ciclo ansioso, por lo que quiere evaluar toda la lista antes de comenzar.

Para simplificar, recomendaría utilizar un objeto iterador o cierre en lugar de intentar tener una matriz evaluada de forma diferida. Si bien puede usar para tener una lista infinitamente evaluada de forma perezosa, puede tener problemas si alguna vez pregunta (directa o indirectamente, como en el foreach anterior) por toda la lista (o incluso el tamaño de toda la lista) .

Sin escribir una clase completa o el uso de cualquiera de los módulos, se puede hacer un simple fábrica de iterador simplemente mediante el uso de cierres:

sub make_iterator { 
    my ($value, $max, $step) = @_; 

    return sub { 
     return if $value > $max; # Return undef when we overflow max. 

     my $current = $value; 
     $value += $step;   # Increment value for next call. 
     return $current;   # Return current iterator value. 
    }; 
} 

Y luego utilizarlo:

# All the even numbers between 0 - 100. 
my $evens = make_iterator(0, 100, 2); 

while (defined(my $x = $evens->())) { 
    print "$x\n"; 
} 

También existe la Tie::Array::Lazy módulo en el CPAN, que proporciona una interfaz mucho más rica y completa para arrays perezosos. No he usado el módulo yo mismo, por lo que su kilometraje puede variar.

Todo lo mejor,

Paul

+1

Si desea obtener más información acerca de este tipo de programación, lea el libro de Mark Jason Dominus "Higher Order Perl". Muy bien, en mi humilde opinión. – moritz

+2

for/foreach do * not * obtiene la lista completa en el caso especial del operador de rango. – user11318

2

Si no recuerdo mal, para/foreach se obtiene primero toda la lista, de todos modos, por lo que una lista perezosamente evaluada se leería por completo y luego comenzaría a recorrer los elementos. Por lo tanto, creo que no hay otra manera que usar un ciclo while. Pero puedo estar equivocado.

La ventaja de un bucle while es que se puede falsificar la sensación de una lista con pereza evaluado con un código de referencia:

my $list = sub { return calculate_next_element }; 
while(defined(my $element = &$list)) { 
    ... 
} 

Después de todo, supongo que un empate es lo más cerca que se puede obtener en Perl 5.

+0

¿Por qué no solo mi $ list = \ & calculate_next_element; ? ¿O omitir la referencia del código y llamar a calculate_next_element directamente? – cjm

+0

for/foreach do * not * obtiene la lista completa en el caso del operador de rango. De lo contrario, lo hacen. – user11318

+0

cjm: Eso solo se entendía como un marcador de posición para * calculate-your-next-element-here *, en realidad no es una llamada a función. De lo contrario, tienes razón, por supuesto. – jkramer

5

Hay al menos un caso especial en el que para y foreach se han optimizado para no generar toda la lista a la vez. Y ese es el operador de rango. Por lo tanto usted tiene la opción de decir:

for my $i (0..$#list) { 
    my $item = some_function($list[$i]); 
    ... 
} 

y esto va a iterar a través de la matriz, transformado como usted quiera, sin necesidad de crear una larga lista de valores en la delantera.

Si desea que su declaración del mapa para devolver un número variable de elementos, se puede hacer esto en su lugar:

for my $i (0..$#array) { 
    for my $item (some_function($array[$i])) { 
    ... 
    } 
} 

Si desea pereza más penetrante que esto, entonces su mejor opción es aprender a utilizar cierres para generar listas perezosas. El excelente libro de MJD Higher Order Perl puede guiarte a través de esas técnicas. Sin embargo, tenga en cuenta que implicarán cambios mucho mayores a su código.

9

[Nota al margen: Tenga en cuenta que cada paso a lo largo de una cadena de mapa/grep está ansioso. Si le da una gran lista a la vez, sus problemas comienzan mucho antes que en el foreach final.]

Lo que puede hacer para evitar una reescritura completa es envolver su bucle con un bucle externo. En lugar de escribir esto:

for my $item (map { ... } grep { ... } map { ... } @list) { ... } 

... escribir así:

while (my $input = calculcate_next_element()) { 
    for my $item (map { ... } grep { ... } map { ... } $input) { ... } 
} 

Esto le ahorra tener que volver a escribir de manera significativa su código existente, y siempre que la lista no crece en varios órdenes de magnitud durante la transformación, se obtiene casi todo el beneficio que ofrecería un estilo de reescritura a iterador.

7

Si quiere hacer listas perezosas, tendrá que escribir su propio iterador. Una vez que tenga eso, puede usar algo como Object::Iterate que tiene versiones de iterator-aware de map y grep. Eche un vistazo a la fuente de ese módulo: es bastante simple y verá cómo escribir sus propias subrutinas compatibles con iteradores.

Buena suerte, :)

3

me hizo una pregunta similar en perlmonks.org y BrowserUk dio un muy buen marco in his answer. Básicamente, una forma conveniente de obtener una evaluación perezosa es generar hilos para el cálculo, al menos mientras esté seguro de que quiere los resultados, Justo ahora. Si desea una evaluación diferida para no reducir la latencia sino para evitar cálculos, mi enfoque no ayudará porque se basa en un modelo push, no en un modelo pull. Posiblemente usando Coro Outines, puede convertir este enfoque en un modelo de extracción (de un solo hilo) también.

Mientras analizaba este problema, que también investigó tie-ing una matriz a los resultados de rosca para hacer fluir el programa Perl más como map, pero hasta ahora, me gusta mi API de introducir la "palabra clave" parallel (un constructor de objetos disfrazado) y luego llamar a métodos sobre el resultado. La versión más documentada del código se publicará como respuesta al that thread y posiblemente también se publique en CPAN.

4

Llevar esta detrás de los muertos mencionar que solo escribí el módulo List::Gen en CPAN que hace exactamente lo que el cartel estaba buscando:

use List::Gen; 

for my $item (@{gen { ... } \@list}) {...} 

todos los cálculos de las listas son perezosos, y hay un mapa/grep equivalentes junto con algunas otras funciones.

cada una de las funciones devuelve un 'generador' que es una referencia a un conjunto vinculado. puede usar el conjunto vinculado directamente, o hay un conjunto de métodos de acceso como iteradores para usar.

+0

Lo echaré un vistazo. Gracias. – Axeman

+0

No hay problema, si hay alguna característica que cree que debería estar en ella, házmelo saber. –

Cuestiones relacionadas