perl lazylist source-filter

¿Existe una solución Perl para listas perezosas a este lado de Perl 6?



lazylist source-filter (8)

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

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

en una evaluación perezosa - por tie-ing @list, 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 debería mencionar que estoy algo atrapado en 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 empaquetar más funcionalidades en el mismo espacio vertical.


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

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

Sin escribir una clase completa o usar ningún módulo, puede hacer una fábrica simple de iteradores simplemente usando 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 para usarlo:

# All the even numbers between 0 - 100. my $evens = make_iterator(0, 100, 2); while (defined( my $x = $evens->() ) ) { print "$x/n"; }

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

Todo lo mejor,

Pablo


Hay al menos un caso especial donde se han optimizado para y foreach para no generar toda la lista a la vez. Y ese es el operador de rango. Entonces tienes la opción de decir:

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

y esto se repetirá a lo largo de la matriz, transformado como quiera, sin crear una larga lista de valores por adelantado.

Si desea que su extracto de mapa devuelva números variables de elementos, puede hacer esto en su lugar:

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

Si desea una pereza más penetrante que esta, entonces su mejor opción es aprender a usar 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.


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

La ventaja de un ciclo while es que puedes fingir la sensación de una lista perezosamente evaluada con una referencia de código:

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

Después de todo, creo que un empate es lo más cerca que puedes llegar en Perl 5.


[Nota: Tenga en cuenta que cada paso individual a lo largo de una cadena de mapa / grep está ansioso. Si le das una gran lista de una sola vez, tus 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 ) { ... }

... escríbelo así:

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

Esto le ahorra tener que reescribir significativamente su código existente, y siempre que la lista no crezca en varios órdenes de magnitud durante la transformación, obtendrá casi todos los beneficios que ofrecería un estilo de reescritura a iterador.



Hice una pregunta similar en perlmonks.org , y BrowserUk brindó un marco muy bueno en su respuesta . 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 salidas Coro , puede convertir este enfoque en un modelo de arrastre (de un solo hilo) también.

Mientras meditaba sobre este problema, también investigué un arreglo de los resultados del hilo para hacer que el programa Perl fluyera más como un map , pero hasta ahora, me gusta mi API de introducir la "palabra clave" parallel (un constructor de objetos disfrazado) y luego llamando a métodos sobre el resultado. La versión más documentada del código se publicará como respuesta a ese hilo y posiblemente también se publique en CPAN.


Retomando esto de la muerte para mencionar que acabo de escribir el módulo List::Gen en CPAN que hace exactamente lo que el afiche estaba buscando:

use List::Gen; for my $item ( @{gen { ... } /@list} ) {...}

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

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


Si quieres hacer listas perezosas, tendrás que escribir tu propio iterador. Una vez que tenga eso, puede usar algo como Object :: Iterate que tiene versiones de map y grep compatibles con iteradores. 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, :)