Evaluación Perezosa De Argumentos De Función

Informar de un error

Si detecta un problema en esta página, haga clic aquí para crear un problema de Bugzilla.

Mejora esta página

Bifurca rápidamente, edita en línea y envía una solicitud de extracción para esta página.Requiere una cuenta de GitHub con inicio de sesión. Esto funciona bien para pequeños cambios.Si desea hacer cambios más grandes, puede considerar usar un clon local.

por Walter Bright, http://www.digitalmars.com/d

La evaluación perezosa es la técnica de no evaluar una expresiónunless y hasta que se requiera el resultado de la expresión.El &&, || y ?: los operadores son la forma convencional de hacer evaluación perezosa:

void test(int* p){ if (p && p) ...}

La segunda expresión p no se evalúa a menos que pis no sea null.Si la segunda expresión no se evaluaba perezosamente, generaría un error de tiempo de ejecución si p era nulo.

Si bien son invaluables, los operadores de evaluación perezosos tienen limitaciones significativas. Considere una función de registro, que registra un mensaje, y puede activarse y desactivarse en tiempo de ejecución en función de un valor global:

void log(const(char) message){ if (logging) fwritefln(logfile, message);}

A menudo, la cadena de mensaje se construirá en tiempo de ejecución:

void foo(int i){ log("Entering foo() with i set to " ~ toString(i));}

Si bien esto funciona, el problema es que la construcción de la secuencia de mensajes ocurre independientemente de si el registro está habilitado o no.Con aplicaciones que hacen un uso intensivo de la tala, esto puede convertirse en una terrible pérdida de rendimiento.

Una forma de solucionarlo es mediante la evaluación perezosa:

void foo(int i){ if (logging) log("Entering foo() with i set to " ~ toString(i));}

pero esto viola los principios de encapsulación al exponer los detalles del registro al usuario. En C, este problema a menudo se trabaja en torno al uso de una macro:

#define LOG(string) (logging && log(string))

pero eso es sólo documentación sobre el problema. Las macros de preprocesador tienen deficiencias conocidas:

  • La variable de registro se expone en el espacio de nombres del usuario.
  • Las macros son invisibles para los depuradores simbólicos.
  • Las Macros son solo globales y no pueden ser de ámbito.
  • Las macros no pueden ser miembros de la clase.
  • A las Macros no se les puede tomar su dirección, por lo que no se pueden pasar indirectamente como las funciones.

Una solución robusta sería una forma de hacer una evaluación perezosa de los parámetros de la función. Tal forma es posible en el lenguaje de programación D usando un parámetro delegado:

void log(const(char) delegate() dg){ if (logging) fwritefln(logfile, dg());}void foo(int i){ log( { return "Entering foo() with i set to " ~ toString(i); });}

Ahora, la expresión de construcción de cadenas solo se evalúa si logginges true y se mantiene la encapsulación. El único problema es que pocos van a querer envolver expresiones con { return exp;}.

Así que D da un paso pequeño, pero crucial, más allá (sugerido por Andrei Alexandrescu).Cualquier expresión se puede convertir implícitamente en un delegado que devuelve void o el tipo de expresión.La declaración de delegado se sustituye por la clase de almacenamiento perezoso(sugerida por Tomasz Stachowiak).Las funciones se convierten en:

void log(lazy const(char) dg){ if (logging) fwritefln(logfile, dg());}void foo(int i){ log("Entering foo() with i set to " ~ toString(i));}

que es nuestra versión original, excepto que ahora la cadena no está construida a menos que el registro esté activado.

Cada vez que se ve un patrón repetido en el código, ser capaz de extraer ese patrón y encapsularlo significa que podemos reducir la complejidad del código y, por lo tanto, los errores. El ejemplo más común de esto es la propia función.La evaluación perezosa permite encapsular una serie de otros patrones.

Para un ejemplo simple, supongamos que una expresión se evalúa en tiempos de cuenta. El patrón es:

for (int i = 0; i < count; i++) exp;

Este patrón se puede encapsular en una función mediante evaluación perezosa:

void dotimes(int count, lazy void exp){ for (int i = 0; i < count; i++) exp();}

Se puede utilizar como:

void foo(){ int x = 0; dotimes(10, write(x++));}

que imprimirá:

0123456789

Son posibles estructuras de control definidas por el usuario más complejas.Aquí hay un método para crear una estructura similar a un interruptor:

bool scase(bool b, lazy void dg){ if (b) dg(); return b;}/* Here the variadic arguments are converted to delegates in this special case. */void cond(bool delegate() cases ...){ foreach (c; cases) { if (c()) break; }}

que se puede utilizar como:

void foo(){ int v = 2; cond ( scase(v == 1, writeln("it is 1")), scase(v == 2, writeln("it is 2")), scase(v == 3, writeln("it is 3")), scase(true, writeln("it is the default")) );}

que imprimirá:

it is 2

Aquellos familiarizados con el lenguaje de programación Lisp notarán algunos paralelismos con las macros Lisp.

Para un último ejemplo, está el patrón común:

Abc p;p = foo();if (!p) throw new Exception("foo() failed");p.bar(); // now use p

Dado que throw es una instrucción, no una expresión, las expresiones que necesitan hacer esto deben dividirse en varias instrucciones y se introducen variables adicionales.(Para un tratamiento exhaustivo de esta cuestión, ver los documentos de Andrei Alexandrescu y Petru Marginean).Con la evaluación perezosa, todo esto se puede encapsular en una sola función:

Abc Enforce(Abc p, lazy const(char) msg){ if (!p) throw new Exception(msg()); return p;}

y el ejemplo de apertura anterior se convierte en simple:

Enforce(foo(), "foo() failed").bar();

y 5 líneas de código se convierten en una. Enforce se puede mejorar convirtiéndolo en una función atemplate:

T Enforce(T)(T p, lazy const(char) msg){ if (!p) throw new Exception(msg()); return p;}

Conclusión

La evaluación perezosa de argumentos de función extiende dramáticamente el poder expresivo de las funciones. Permite la encapsulación en funciones de muchos patrones de codificación comunes y expresiones idiomáticas que anteriormente eran demasiado torpes o prácticas de hacer.

Agradecimientos

Agradezco la inspiración y asistencia de Andrei Alexandrescu, Bartosz Milewski y David Held. La comunidad D ayudó mucho con muchas críticas constructivas, como el hilo que comienza con Tomasz Stachowiak en D/41633.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.