Pregunta

¿Alguien ha encontrado una buena solución para listas evaluadas de forma diferida en Perl?He probado varias formas de convertir algo como

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

en una evaluación diferida, vinculando @list, por ejemplo.Estoy tratando de evitar desglosar y escribir un filtro de fuente para hacerlo, porque afectan su capacidad para depurar el código.¿Alguien ha tenido algún éxito?¿O simplemente tienes que descomponerte y usar un bucle while?

Nota: Supongo que debo mencionar que estoy un poco enganchado a cadenas grep-map a veces largas para transformar listas funcionalmente.Así que no se trata tanto del bucle foreach ni del bucle while.Es que las expresiones de mapas tienden a incluir más funciones en el mismo espacio vertical.

¿Fue útil?

Solución

Como se mencionó anteriormente, for(each) es un bucle ansioso, por lo que desea evaluar la lista completa antes de comenzar.

Para simplificar, recomendaría usar un objeto iterador o un cierre en lugar de intentar tener una matriz evaluada de forma diferida.Mientras tu poder use un empate para tener una lista infinita evaluada de manera perezosa, puede tener problemas si alguna vez pregunta (directa o indirectamente, como en el foreach anterior) la lista completa (o incluso el tamaño de la lista completa).

Sin escribir una clase completa ni utilizar ningún módulo, puedes crear una fábrica de iteradores simple 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 Corbata::Matriz::Perezosa módulo en CPAN, que proporciona una interfaz mucho más rica y completa para arreglos diferidos.Yo no he usado el módulo, por lo que su kilometraje puede variar.

Mis mejores deseos,

Pablo

Otros consejos

[Nota al margen:Tenga en cuenta que cada paso individual a lo largo de una cadena de mapa/grep es entusiasta.Si le das una lista grande de una vez, tus problemas comenzarán mucho antes que en el final. foreach.]

Lo que puedes hacer para evitar una reescritura completa es envolver tu 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 evita 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 una reescritura al estilo iterador.

Si desea crear listas diferidas, deberá escribir su propio iterador.Una vez que tengas eso, puedes usar algo como Objeto::Iterar que tiene versiones compatibles con iteradores de map y grep.Eche un vistazo a la fuente de ese módulo:Es bastante simple y verás cómo escribir tus propias subrutinas compatibles con iteradores.

Buena suerte, :)

Hay al menos un caso especial en el que for y foreach se han optimizado para no generar la lista completa 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 iterará a través de la matriz, transformada como desee, sin crear una larga lista de valores por adelantado.

Si desea que su declaración 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 generalizada que esta, entonces su mejor opción es aprender a usar cierres para generar listas diferidas.Excelente libro de MJD Perl de orden superior puede guiarle a través de esas técnicas.Sin embargo, tenga en cuenta que implicarán cambios mucho mayores en su código.

Trayendo esto de entre los muertos para mencionar que acabo de escribir el módulo. List::Gen en CPAN que hace exactamente lo que buscaba el cartel:

use List::Gen;

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

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

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

Usa un iterador o considere usar Corbata::LazyList de CPAN (que está un poco anticuado).

Hice una pregunta similar en perlmonks.org, y BrowserUk brindó un marco realmente bueno en su respuesta.Básicamente, una forma conveniente de obtener una evaluación diferida es generar subprocesos para el cálculo, al menos mientras esté seguro de querer los resultados, pero ahora no.Si desea que la evaluación diferida no reduzca la latencia sino que evite los cálculos, mi enfoque no ayudará porque se basa en un modelo push, no en un modelo pull.Posiblemente usando Coroesquemas, también puede convertir este enfoque en un modelo de extracción (de un solo subproceso).

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

Si no recuerdo mal, for/foreach obtiene la lista completa primero de todos modos, por lo que una lista evaluada de forma diferida 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 forma que usar un bucle while.Pero puedo estar equivocado.

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

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

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top