Estrategia de evaluación

Reglas de evaluación de lenguajes de programación

En un lenguaje de programación , una estrategia de evaluación es un conjunto de reglas para evaluar expresiones. [1] El término se utiliza a menudo para referirse a la noción más específica de una estrategia de paso de parámetros [2] que define el tipo de valor que se pasa a la función para cada parámetro (la estrategia de enlace ) [3] y si se deben evaluar los parámetros de una llamada de función, y si es así en qué orden (el orden de evaluación ). [4] La noción de estrategia de reducción es distinta, [5] aunque algunos autores confunden los dos términos y la definición de cada término no es ampliamente aceptada. [6]

Para ilustrar, la ejecución de una llamada de función f(a,b)puede evaluar primero los argumentos ay b, almacenar los resultados en referencias o ubicaciones de memoria ref_ay ref_b, luego evaluar el cuerpo de la función con esas referencias pasadas. Esto le da a la función la capacidad de buscar los valores de los argumentos originales pasados ​​mediante la desreferenciación de los parámetros (algunos lenguajes usan operadores específicos para realizar esto), modificarlos mediante la asignación como si fueran variables locales y devolver valores mediante las referencias. Esta es la estrategia de evaluación de llamada por referencia. [7]

La estrategia de evaluación es parte de la semántica de la definición del lenguaje de programación. Algunos lenguajes, como PureScript , tienen variantes con diferentes estrategias de evaluación. Algunos lenguajes declarativos , como Datalog , admiten múltiples estrategias de evaluación. Algunos lenguajes definen una convención de llamada . [ Aclaración necesaria ]

Mesa

Esta es una tabla de estrategias de evaluación e idiomas representativos por año de introducción. Los idiomas representativos se enumeran en orden cronológico, comenzando con el idioma o idiomas que introdujeron la estrategia y seguidos por los idiomas destacados que la utilizan. [8] : 434 

Estrategia de evaluaciónIdiomas representativosAño de introducción por primera vez
Llamar por referenciaFortran II, PL/I1958
Llamada por valorALGOL , C , Esquema , MATLAB [9]1960
Llamar por nombreALGOL 60 , Simulacion1960
Llamada por copia-restauraciónFortran IV , Ada [10]1962
Llamado por unificaciónPrólogo1965 [11] [12]
Llamar por necesidadSASL , [13] Haskell , R [14]1971 [15]
Llamar compartiendoCLU , Java , Python , Rubí , Julia1974 [16]
Llamada por parámetros de referenciaC++ , PHP , [17] C# , [18] Visual Basic .NET [19]1985 [20]
Llamada por referencia a constC++ , C1985 [20]

Órdenes de evaluación

Mientras que el orden de las operaciones define el árbol de sintaxis abstracta de la expresión, el orden de evaluación define el orden en el que se evalúan las expresiones. Por ejemplo, el programa Python

def  f ( x ):  imprimir ( x ,  fin = '' )  devolver  ximprimir ( f ( 1 )  +  f ( 2 ), fin = '' )

resultados 123debido al orden de evaluación de izquierda a derecha de Python, pero un programa similar en OCaml :

sea  ​​f  x  =  print_int  x ;  x  ;; print_int  ( f  1  +  f  2 )

resultados 213debido al orden de evaluación de derecha a izquierda de OCaml.

El orden de evaluación es principalmente visible en el código con efectos secundarios , pero también afecta el rendimiento del código porque un orden rígido inhibe la programación de instrucciones . Por esta razón, los estándares de lenguaje como C++ tradicionalmente dejaban el orden sin especificar, aunque lenguajes como Java y C# definen el orden de evaluación como de izquierda a derecha [8] : 240–241  y el estándar C++17 ha agregado restricciones al orden de evaluación. [21]

Evaluación estricta

El orden aplicativo es una familia de órdenes de evaluación en la que los argumentos de una función se evalúan completamente antes de que se aplique la función. [22] Esto tiene el efecto de hacer que la función sea estricta , es decir, el resultado de la función no está definido si alguno de los argumentos no está definido, por lo que la evaluación del orden aplicativo se denomina más comúnmente evaluación estricta . Además, una llamada a una función se realiza tan pronto como se encuentra en un procedimiento, por lo que también se denomina evaluación ansiosa o evaluación codiciosa . [23] [24] Algunos autores se refieren a la evaluación estricta como "llamada por valor" debido a la estrategia de enlace de llamada por valor que requiere una evaluación estricta. [4]

Common Lisp, Eiffel y Java evalúan los argumentos de las funciones de izquierda a derecha. C deja el orden sin definir. [25] Scheme requiere que el orden de ejecución sea la ejecución secuencial de una permutación no especificada de los argumentos. [26] OCaml deja el orden sin especificar, pero en la práctica evalúa los argumentos de derecha a izquierda debido al diseño de su máquina abstracta . [27] Todas estas son evaluaciones estrictas.

Evaluación no estricta

Un orden de evaluación no estricto es un orden de evaluación que no es estricto, es decir, una función puede devolver un resultado antes de que todos sus argumentos se evalúen por completo. [28] : 46–47  El ejemplo prototípico es la evaluación de orden normal , que no evalúa ninguno de los argumentos hasta que se necesitan en el cuerpo de la función. [29] La evaluación de orden normal tiene la propiedad de que termina sin error siempre que cualquier otro orden de evaluación hubiera terminado sin error. [30] El nombre "orden normal" proviene del cálculo lambda, donde la reducción de orden normal encontrará una forma normal si hay una (es una estrategia de reducción "normalizadora" ). [31] La evaluación perezosa se clasifica en este artículo como una técnica vinculante en lugar de un orden de evaluación. Pero esta distinción no siempre se sigue y algunos autores definen la evaluación perezosa como evaluación de orden normal o viceversa, [22] [32] o confunden la no estrictez con la evaluación perezosa. [28] : 43–44 

Las expresiones booleanas en muchos lenguajes utilizan una forma de evaluación no estricta llamada evaluación de cortocircuito , donde la evaluación evalúa la expresión izquierda pero puede omitir la expresión derecha si el resultado puede determinarse, por ejemplo, en una expresión disyuntiva (OR) donde truese encuentra, o en una expresión conjuntiva (AND) donde falsese encuentra, y así sucesivamente. [32] Las expresiones condicionales utilizan de manera similar una evaluación no estricta: solo se evalúa una de las ramas. [28]

Comparación entre la evaluación del orden aplicativo y el orden normal

En la evaluación de orden normal, las expresiones que contienen un cálculo costoso, un error o un bucle infinito se ignorarán si no son necesarias, [4] lo que permite la especificación de construcciones de flujo de control definidas por el usuario, una función que no está disponible en la evaluación de orden aplicativo. La evaluación de orden normal utiliza estructuras complejas, como thunks , para expresiones no evaluadas, en comparación con la pila de llamadas utilizada en la evaluación de orden aplicativo. [33] La evaluación de orden normal históricamente ha carecido de herramientas de depuración utilizables debido a su complejidad. [34]

Estrategias de vinculación estricta

Llamada por valor

En la llamada por valor (o paso por valor), el valor evaluado de la expresión del argumento se vincula a la variable correspondiente en la función (con frecuencia copiando el valor en una nueva región de memoria). Si la función o el procedimiento puede asignar valores a sus parámetros, solo se asigna su variable local; es decir, todo lo que se pasa a una llamada de función no se modifica en el ámbito del invocador cuando la función retorna. Por ejemplo, en Pascal , pasar una matriz por valor hará que se copie toda la matriz y cualquier mutación en esta matriz será invisible para el invocador: [35]

programa Principal ; utiliza crt ;  procedimiento PrintArray ( a : Matriz de enteros ) ; var i : Entero ; inicio para i := Low ( a ) a High ( a ) hacer Write ( a [ i ]) ; WriteLn () ; fin ;               Procedimiento Modificar ( Fila : Matriz de enteros ) ; comenzar PrintArray ( Fila ) ; // 123 Fila [ 1 ] := 4 ; PrintArray ( Fila ) ; // 143 terminar ;              Var A : Matriz de enteros ; inicio A := [ 1 , 2 , 3 ] ; PrintArray ( A ) ; // 123 Modificar ( A ) ; PrintArray ( A ) ; // 123 fin .              

Deriva semántica

Estrictamente hablando, bajo la llamada por valor, ninguna operación realizada por la rutina llamada puede ser visible para el llamador, excepto como parte del valor de retorno. [16] Esto implica una forma de programación puramente funcional en la semántica de implementación. Sin embargo, la circunloquio "llamada por valor donde el valor es una referencia" se ha vuelto común en algunos lenguajes, por ejemplo, la comunidad Java. [36] Comparado con el paso por valor tradicional, el valor que se pasa no es un valor como se entiende por el significado ordinario de valor, como un entero que puede escribirse como un literal, sino un identificador de referencia interno de la implementación . Las mutaciones a este identificador de referencia son visibles en el llamador. Debido a la mutación visible, esta forma de "llamada por valor" se conoce más apropiadamente como llamada por compartir. [16]

En lenguajes puramente funcionales , los valores y las estructuras de datos son inmutables, por lo que no existe la posibilidad de que una función modifique ninguno de sus argumentos. Por lo tanto, normalmente no existe una diferencia semántica entre pasar por valor y pasar por referencia o un puntero a la estructura de datos, y las implementaciones suelen utilizar la llamada por referencia internamente para obtener beneficios de eficiencia. No obstante, estos lenguajes suelen describirse como lenguajes de llamada por valor.

Llamar por referencia

La llamada por referencia (o paso por referencia) es una estrategia de evaluación en la que un parámetro está ligado a una referencia implícita a la variable utilizada como argumento, en lugar de una copia de su valor. Esto normalmente significa que la función puede modificar (es decir, asignar a ) la variable utilizada como argumento, algo que será visto por su llamador. Por lo tanto, la llamada por referencia se puede utilizar para proporcionar un canal adicional de comunicación entre la función llamada y la función que llama. El paso por referencia puede mejorar significativamente el rendimiento: llamar a una función con una estructura de muchos megabytes como argumento no tiene que copiar la estructura grande, solo la referencia a la estructura (que generalmente es una palabra de máquina y solo unos pocos bytes). Sin embargo, un lenguaje de llamada por referencia hace que sea más difícil para un programador rastrear los efectos de una llamada de función y puede introducir errores sutiles.

Debido a la variación en la sintaxis, la diferencia entre una llamada por referencia (donde el tipo de referencia es implícito) y una llamada por uso compartido (donde el tipo de referencia es explícito) a menudo no está clara a primera vista. Una prueba de fuego sencilla es si es posible escribir una swap(a, b)función tradicional en el lenguaje. [36] Por ejemplo, en Fortran:

programa Principal implícito ninguno entero :: a = 1 entero :: b = 2 llamada Swap ( a , b ) imprimir * , a , b ! 2 1 contiene  subrutina Swap ( a , b ) entero , intento ( inout ) :: a , b entero :: temp temp = a a = b b = temp fin subrutina Swap fin programa Principal                                     

Por lo tanto, la intención de Fortran inoutes implementar la llamada por referencia; cualquier variable puede convertirse implícitamente en un identificador de referencia. En cambio, lo más cercano que se puede llegar a esto en Java es:

clase  Main { static class Caja { int valor ; public Caja ( int valor ) { this . valor = valor ; } } static void swap ( Caja a , Caja b ) { int temp = a . valor ; a . valor = b . valor ; b . valor = temp ; } public static void main ( String [] args ) { Caja a = new Caja ( 1 ); Caja b = new Caja ( 2 ); swap ( a , b ); System . out . println ( String . format ( "%d %d" , a . valor , b . valor )); } } // salida: 2 1                                                        

Boxdonde se debe utilizar un tipo explícito para introducir un identificador. Java es de llamada por uso compartido, pero no de llamada por referencia. [36]

Llamada por copia-restauración

La llamada por copia-restauración, también conocida como "copia de entrada, copia de salida", "llamada por resultado de valor", "llamada por retorno de valor" (como se denomina en la comunidad Fortran ), es una variación de la llamada por referencia. Con la llamada por copia-restauración, el contenido del argumento se copia a una nueva variable local a la invocación de la llamada. La función puede modificar esta variable, de manera similar a la llamada por referencia, pero como la variable es local, las modificaciones no son visibles fuera de la invocación de la llamada durante la llamada. Cuando la llamada de la función retorna, el contenido actualizado de esta variable se copia nuevamente para sobrescribir el argumento original ("restaurado"). [37]

La semántica de la llamada por copia-restauración es similar en muchos casos a la llamada por referencia, pero difiere cuando dos o más argumentos de función se alias entre sí (es decir, apuntan a la misma variable en el entorno del llamador). Bajo la llamada por referencia, escribir en un argumento afectará al otro durante la ejecución de la función. Bajo la llamada por copia-restauración, escribir en un argumento no afectará al otro durante la ejecución de la función, pero al final de la llamada, los valores de los dos argumentos pueden diferir, y no está claro qué argumento se copia primero y, por lo tanto, qué valor recibe la variable del llamador. [38] Por ejemplo, Ada especifica que la asignación de copia de salida para cada parámetro in outo outocurre en un orden arbitrario. [39] Del siguiente programa (ilegal en Ada 2012) [40] se puede ver que el comportamiento de GNAT es copiar en orden de izquierda a derecha al regresar:

con  Ada.Text_IO ;  utilizar  Ada.Text_IO ;procedimiento  Test_Copy_Restore  es  procedimiento  Modificar  ( A ,  B  : in  out  Integer )  es  begin  A  :=  A  +  1 ;  B  :=  B  +  2 ;  end  Modificar ;  X  :  Integer  :=  0 ; begin  Modificar ( X ,  X );  Put_Line ( "X = "  &  Integer ' Image ( X )); end  Test_Copy_Restore ; -- $ gnatmake -gnatd.E test_copy_restore.adb; ./test_copy_restore -- test_copy_restore.adb:12:10: advertencia: el valor real escribible para "A" se superpone con el valor real para "B" [-gnatw.i] -- X = 2

Si el programa devolviera 1, estaría copiando de derecha a izquierda y, bajo la semántica de llamada por referencia, el programa devolvería 3.

Cuando la referencia se pasa al llamador sin inicializar (por ejemplo, un outparámetro en Ada en lugar de un in outparámetro), esta estrategia de evaluación puede denominarse "llamada por resultado".

Esta estrategia ha ganado atención en el multiprocesamiento y en las llamadas a procedimientos remotos , [41] ya que, a diferencia de la llamada por referencia, no requiere una comunicación frecuente entre subprocesos de ejecución para el acceso a las variables.

Llamar compartiendo

La llamada por compartición (también conocida como "pasar por compartición", "llamada por objeto" o "llamada por compartición de objetos") es una estrategia de evaluación intermedia entre la llamada por valor y la llamada por referencia. En lugar de que cada variable se exponga como una referencia, solo una clase específica de valores, denominada "referencias", " tipos encajonados " u "objetos", tienen semántica de referencia, y son las direcciones de estos punteros las que se pasan a la función. Al igual que la llamada por valor, el valor de la dirección pasada es una copia, y la asignación directa al parámetro de la función sobrescribe la copia y no es visible para la función que realiza la llamada. Al igual que la llamada por referencia, la mutación del objetivo del puntero es visible para la función que realiza la llamada. Las mutaciones de un objeto mutable dentro de la función son visibles para el que realiza la llamada porque el objeto no se copia ni se clona, ​​se comparte , de ahí el nombre "llamada por compartición". [16]

La técnica fue descrita por primera vez por Barbara Liskov en 1974 para el lenguaje CLU . [16] La utilizan muchos lenguajes modernos como Python (los valores compartidos se denominan "objetos"), [42] Java (objetos), Ruby (objetos), JavaScript (objetos), Scheme (estructuras de datos como vectores), [43] AppleScript (listas, registros, fechas y objetos de script), OCaml y ML (referencias, registros, matrices, objetos y otros tipos de datos compuestos), Maple (rtables y tablas) y Tcl (objetos). [44] El término "llamada por compartición" tal como se utiliza en este artículo no es de uso común; la terminología es inconsistente en diferentes fuentes. Por ejemplo, en la comunidad Java, dicen que Java es una llamada por valor. [36]

En el caso de objetos inmutables , no existe una diferencia real entre la llamada por compartición y la llamada por valor, excepto si la identidad del objeto es visible en el lenguaje. El uso de la llamada por compartición con objetos mutables es una alternativa a los parámetros de entrada/salida : el parámetro no se asigna (el argumento no se sobrescribe y la identidad del objeto no se modifica), pero el objeto (argumento) se muta. [45]

Por ejemplo, en Python, las listas son mutables y se pasan con llamadas compartidas, así:

def  f ( una_lista ) :  una_lista .append ( 1 )m  =  [] f ( m ) imprimir ( m )

salidas [1]porque el appendmétodo modifica el objeto en el que se llama.

Por el contrario, las asignaciones dentro de una función no son visibles para el que llama. Por ejemplo, este código vincula el argumento formal a un nuevo objeto, pero no es visible para el que llama porque no muta a_list:

def  f ( una_lista ):  una_lista  =  una_lista  +  [ 1 ]  print ( una_lista )  # [1]m  =  [] f ( m ) imprimir ( m )  # []

Llamar por dirección

Llamar por dirección , pasar por dirección o llamar/pasar por puntero es un método de paso de parámetros en el que la dirección del argumento se pasa como parámetro formal. Dentro de la función, la dirección (puntero) se puede utilizar para acceder o modificar el valor del argumento. Por ejemplo, la operación de intercambio se puede implementar de la siguiente manera en C: [46]

#incluir <stdio.h> void swap ( int * a , int * b ) { int temp = * a ; * a = * b ; * b = temp ; }               int main () { int a = 1 ; int b = 2 ; intercambiar ( &a a , & b ); printf ( "%d %d" , a , b ); // 2 1 devuelve 0 ; }                  

Algunos autores tratan &como parte de la sintaxis de la llamada swap. Bajo esta perspectiva, C admite la estrategia de paso de parámetros de llamada por referencia. [47] Otros autores adoptan una visión diferente de que la implementación presentada de swapen C es solo una simulación de llamada por referencia utilizando punteros. [48] Bajo esta visión de "simulación", las variables mutables en C no son de primera clase (es decir, los valores l no son expresiones), sino que lo son los tipos de puntero. En esta visión, el programa de intercambio presentado es azúcar sintáctico para un programa que utiliza punteros en todo momento, [49] por ejemplo este programa ( ready assignse han agregado para resaltar las similitudes con el Boxprograma de llamada por intercambio de Java anterior):

#incluir <stdio.h> int leer ( int * p ) { devolver * p ; }     void asignar ( int * p , int v ) { * p = v ; }        void swap ( int * a , int * b ) { int temp_storage ; int * temp = & temp_storage ; asignar ( temp , leer ( a )); asignar ( a , leer ( b )); asignar ( b , leer ( temp )); }                 int main () { int a_almacenamiento ; int * a = & a_almacenamiento ; int b_almacenamiento ; int * b = & b_almacenamiento ; asignar ( a , 1 ); asignar ( b , 2 ); intercambiar ( a , b ); printf ( "%d %d" , leer ( a ), leer ( b )); // 2 1 devolver 0 ; }                        

Debido a que en este programa swapse opera sobre punteros y no se pueden cambiar los punteros en sí, sino solamente los valores a los que apuntan los punteros, este punto de vista sostiene que la estrategia de evaluación principal de C es más similar a la llamada por intercambio.

C++ complica aún más la cuestión al permitir swapque se declare y utilice con una sintaxis de "referencia" muy ligera: [50]

void swap ( int & a , int & b ) { int temp = a ; a = b ; b = temp ; }               int main () { int a = 1 ; int b = 2 ; intercambiar ( a , b ); std :: cout << a << b << std :: endl ; // 2 1 devolver 0 ; }                      

Semánticamente, esto es equivalente a los ejemplos de C. Como tal, muchos autores consideran que la llamada por dirección es una estrategia de paso de parámetros única, distinta de la llamada por valor, la llamada por referencia y la llamada por uso compartido.

Llamado por unificación

En programación lógica , la evaluación de una expresión puede corresponder simplemente a la unificación de los términos involucrados combinada con la aplicación de alguna forma de resolución . La unificación debe clasificarse como una estrategia de vinculación estricta porque se realiza en su totalidad. Sin embargo, la unificación también se puede realizar en variables no acotadas, por lo que las llamadas pueden no necesariamente comprometerse con los valores finales para todas sus variables.

Estrategias de vinculación no estricta

Llamar por nombre

La llamada por nombre es una estrategia de evaluación en la que los argumentos de una función no se evalúan antes de llamar a la función, sino que se sustituyen directamente en el cuerpo de la función (mediante la sustitución que evita la captura ) y luego se dejan para que se evalúen cada vez que aparecen en la función. Si un argumento no se utiliza en el cuerpo de la función, nunca se evalúa; si se utiliza varias veces, se vuelve a evaluar cada vez que aparece. (Consulte el dispositivo de Jensen para obtener una técnica de programación que aprovecha esto).

En ocasiones, la evaluación por nombre es preferible a la evaluación por valor. Si el argumento de una función no se utiliza en la función, la llamada por nombre ahorrará tiempo al no evaluar el argumento, mientras que la llamada por valor lo evaluará de todas formas. Si el argumento es un cálculo no terminal, la ventaja es enorme. Sin embargo, cuando se utiliza el argumento de la función, la llamada por nombre suele ser más lenta y requiere un mecanismo como un thunk .

Los lenguajes .NET pueden simular llamadas por nombre utilizando delegados o Expression<T>parámetros. Esto último da como resultado un árbol de sintaxis abstracta que se le proporciona a la función. Eiffel proporciona agentes, que representan una operación que se evaluará cuando sea necesario. Seed7 proporciona llamadas por nombre con parámetros de función. Los programas Java pueden lograr una evaluación diferida similar utilizando expresiones lambda y la java.util.function.Supplier<T>interfaz.

Llamar por necesidad

La llamada por necesidad es una variante memorizada de la llamada por nombre, donde, si se evalúa el argumento de la función, ese valor se almacena para su uso posterior. Si el argumento es puro (es decir, libre de efectos secundarios), esto produce los mismos resultados que la llamada por nombre, ahorrando el costo de volver a calcular el argumento.

Haskell es un lenguaje conocido que utiliza la evaluación por necesidad. Debido a que la evaluación de expresiones puede ocurrir arbitrariamente en un momento dado de un cálculo, Haskell solo admite efectos secundarios (como la mutación ) mediante el uso de mónadas . Esto elimina cualquier comportamiento inesperado de las variables cuyos valores cambian antes de su evaluación retrasada.

En la implementación de llamada por necesidad de R , se pasan todos los argumentos, lo que significa que R permite efectos secundarios arbitrarios.

La evaluación perezosa es la implementación más común de la semántica de llamada por necesidad, pero existen variaciones como la evaluación optimista. Los lenguajes .NET implementan la llamada por necesidad utilizando el tipo Lazy<T>.

La reducción de gráficos es una implementación eficiente de la evaluación perezosa.

Llamado por expansión macro

La expansión de llamadas por macro es similar a la llamada por nombre, pero utiliza la sustitución textual en lugar de la sustitución que evita la captura . Por lo tanto, la sustitución de macro puede dar como resultado la captura de variables, lo que genera errores y un comportamiento no deseado. Las macros higiénicas evitan este problema al verificar y reemplazar las variables sombreadas que no son parámetros.

Llamada por futuro

La "llamada por futuro", también conocida como "llamada paralela por nombre" o "evaluación indulgente", [51] es una estrategia de evaluación concurrente que combina una semántica no estricta con una evaluación diligente. El método requiere una programación y sincronización dinámicas de grano fino, pero es adecuado para máquinas masivamente paralelas.

La estrategia crea un futuro (promesa) para el cuerpo de la función y cada uno de sus argumentos. Estos futuros se calculan simultáneamente con el flujo del resto del programa. Cuando un futuro A requiere el valor de otro futuro B que aún no se ha calculado, el futuro A se bloquea hasta que el futuro B termina de calcularse y tiene un valor. Si el futuro B ya ha terminado de calcularse, el valor se devuelve inmediatamente. Los condicionales se bloquean hasta que se evalúa su condición y las lambdas no crean futuros hasta que se aplican por completo. [52]

Si se implementa con procesos o subprocesos, la creación de un futuro generará uno o más procesos o subprocesos nuevos (para las promesas), el acceso al valor los sincronizará con el subproceso principal y la finalización del cálculo del futuro corresponde a la eliminación de las promesas que calculan su valor. Si se implementa con una corrutina , como en .NET async/await , la creación de un futuro llama a una corrutina (una función asincrónica), que puede ceder el paso al que llama y, a su vez, ceder el paso al que llama cuando se usa el valor, realizando múltiples tareas de manera cooperativa.

La estrategia no es determinista, ya que la evaluación puede ocurrir en cualquier momento entre la creación del futuro (es decir, cuando se proporciona la expresión) y el uso del valor del futuro. La estrategia no es estricta porque el cuerpo de la función puede devolver un valor antes de que se evalúen los argumentos. Sin embargo, en la mayoría de las implementaciones, la ejecución puede quedarse atascada al evaluar un argumento innecesario. Por ejemplo, el programa

f x = 1 / x g y = 1 principal = imprimir ( g ( f 0 ))           

puede gterminar antes de f, y generar 1, o puede generar un error debido a la evaluación de 1/0. [28]

La llamada por futuro es similar a la llamada por necesidad en el sentido de que los valores se calculan solo una vez. Con un manejo cuidadoso de los errores y la no terminación, en particular terminando los futuros a mitad de camino si se determina que no serán necesarios, la llamada por futuro también tiene las mismas propiedades de terminación que la evaluación de llamada por necesidad. [52] Sin embargo, la llamada por futuro puede realizar un trabajo especulativo innecesario en comparación con la llamada por necesidad, como la evaluación profunda de una estructura de datos perezosa. [28] Esto se puede evitar utilizando futuros perezosos que no comienzan el cálculo hasta que se tiene la certeza de que el valor es necesario.

Evaluación optimista

La evaluación optimista es una variante de llamada por necesidad en la que el argumento de la función se evalúa parcialmente en un estilo de llamada por valor durante un período de tiempo (que se puede ajustar en tiempo de ejecución ). Una vez transcurrido ese tiempo, se cancela la evaluación y se aplica la función mediante llamada por necesidad. [53] Este enfoque evita algunos de los gastos de tiempo de ejecución de la llamada por necesidad al tiempo que conserva las características de terminación deseadas.

Véase también

Referencias

  1. ^ Araki, Shota; Nishizaki, Shin-ya (noviembre de 2014). "Evaluación por nombre de cálculos RPC y RMI". Teoría y práctica de la computación. p. 1. doi :10.1142/9789814612883_0001. ISBN 978-981-4612-87-6. Recuperado el 21 de agosto de 2021 .
  2. ^ Turbak, Franklyn; Gifford, David (18 de julio de 2008). Conceptos de diseño en lenguajes de programación. MIT Press. p. 309. ISBN 978-0-262-30315-6.
  3. ^ Crank, Erik; Felleisen, Matthias (1991). "Paso de parámetros y cálculo lambda". Actas del 18.º simposio ACM SIGPLAN-SIGACT sobre Principios de lenguajes de programación - POPL '91 . pág. 2. CiteSeerX 10.1.1.23.4385 . doi :10.1145/99583.99616. ISBN .  0897914198.S2CID 5782416  .
  4. ^ abc Wilhelm, Reinhard; Seidl, Helmut (10 de noviembre de 2010). Diseño de compiladores: máquinas virtuales. Medios de ciencia y negocios de Springer. pag. 61.ISBN 978-3-642-14909-2.
  5. ^ Nita, Stefanía Loredana; Mihailescu, Marius (2017). "Introducción". Haskell concurrente práctico . pag. 3. doi :10.1007/978-1-4842-2781-7_1. ISBN 978-1-4842-2780-0.
  6. ^ Pierce, Benjamin C. (2002). Tipos y lenguajes de programación. MIT Press . pág. 56. ISBN. 0-262-16209-1.
  7. ^ Daniel P. Friedman; Mitchell Wand (2008). Fundamentos de lenguajes de programación (tercera edición). Cambridge, MA: The MIT Press . ISBN 978-0262062794.
  8. ^ ab Scott, Michael Lee (2016). Pragmática de los lenguajes de programación (cuarta edición). Waltham, MA: Elsevier. ISBN 9780124104778.
  9. ^ "Evite copias innecesarias de datos: MATLAB y Simulink". www.mathworks.com . Consultado el 28 de enero de 2023 .
  10. ^ Hasti, Rebecca. "Paso de parámetros". CS 536: Introducción a los lenguajes de programación y compiladores . Universidad de Wisconsin . Consultado el 22 de agosto de 2021 .
  11. ^ JA Robinson (enero de 1965). "Una lógica orientada a la máquina basada en el principio de resolución". Revista de la ACM . 12 (1): 23–41. doi : 10.1145/321250.321253 . S2CID  14389185.; Aquí: secc.5.8, p.32
  12. ^ JA Robinson (1971). "Lógica computacional: la computación de unificación". Inteligencia artificial . 6 : 63–72.
  13. ^ Bundy, Alan; Wallen, Lincoln (1984). "SASL". Catálogo de herramientas de inteligencia artificial . p. 117. doi :10.1007/978-3-642-96868-6_222. ISBN 978-3-540-13938-6Fue probablemente el primer lenguaje en explotar sistemáticamente el poder de la evaluación perezosa.
  14. ^ Fay, Colin (30 de julio de 2018). «Acerca de la evaluación perezosa». R-bloggers . Consultado el 21 de agosto de 2021 .
  15. ^ Wadsworth, Christopher P. (1971). Semántica y pragmática del cálculo lambda (PhD). Universidad de Oxford.
  16. ^ abcde Liskov, Barbara; Atkinson, Russ; Bloom, Toby; Moss, Eliot; Schaffert, Craig; Scheifler, Craig; Snyder, Alan (octubre de 1979). «CLU Reference Manual» (PDF) . Laboratorio de Ciencias de la Computación. Instituto Tecnológico de Massachusetts. págs. 14-15. Archivado (PDF) desde el original el 22 de septiembre de 2006. Consultado el 19 de mayo de 2011 .
  17. ^ "PHP: Pasar por referencia - Manual" www.php.net . Consultado el 4 de julio de 2021 .
  18. ^ Wagner, Bill (12 de abril de 2023). "Pasando parámetros - Guía de programación de C#". Microsoft Docs . Consultado el 10 de septiembre de 2023 .
  19. ^ Dollard, Kathleen (15 de septiembre de 2021). "Pasar argumentos por valor y por referencia - Visual Basic". Microsoft Docs . Consultado el 10 de septiembre de 2023 .
  20. ^ ab "Historia de C++". en.cppreference.com . Consultado el 11 de junio de 2022 .
  21. ^ Filipek, Bartlomiej (16 de agosto de 2021). "Orden de evaluación de expresiones más estricto en C++17". Historias de C++ . Consultado el 24 de agosto de 2021 .
  22. ^ ab Abelson, Harold ; Sussman, Gerald Jay (1996). "Orden normal y orden aplicativo". Estructura e interpretación de programas informáticos (2.ª ed.). Cambridge, Massachusetts: MIT Press . ISBN 0-262-01153-0Archivado desde el original el 2 de marzo de 2005. Consultado el 6 de marzo de 2006 .Véase también la nota Temp 576.
  23. ^ Reese, Richard M. (14 de octubre de 2015). Aprendizaje de la programación funcional en Java. Packt Publishing Ltd. pág. 106. ISBN 978-1-78528-935-4.
  24. ^ Antani, Ved; Timms, Simon; Mantyla, Dan (31 de agosto de 2016). JavaScript: programación funcional para desarrolladores de JavaScript. Packt Publishing Ltd. pág. 614. ISBN 978-1-78712-557-5.
  25. ^ Seacord, Robert C. "EXP30-C. No dependa del orden de evaluación para detectar efectos secundarios". Estándar de codificación SEI CERT C. Universidad Carnegie Mellon . Consultado el 23 de agosto de 2021 .
  26. ^ Anglade, S.; Lacrampe, JJ; Queinnec, C. (octubre de 1994). "Semántica de combinaciones en Schema" (PDF) . ACM SIGPLAN Lisp Pointers . VII (4): 15–20. doi :10.1145/382109.382669. S2CID  2987427.
  27. ^ "¿Por qué los argumentos de las funciones OCaml se evalúan de derecha a izquierda?". OCaml . 30 de noviembre de 2017.
  28. ^ abcde Tremblay, G. (abril de 2000). "La evaluación indulgente no es ni estricta ni perezosa". Lenguajes informáticos . 26 (1): 43–66. CiteSeerX 10.1.1.137.9885 . doi :10.1016/S0096-0551(01)00006-6. 
  29. ^ George, Lai (marzo de 1987). Evaluación eficiente del orden normal a través de información de estrictez (MSc). Universidad de Utah. p. 10.
  30. ^ Borning, Alan (otoño de 1999). "Evaluación de orden normal frente a aplicativa en lenguajes funcionales" (PDF) . CSE 505: Conceptos de lenguajes de programación . Universidad de Washington . Consultado el 23 de agosto de 2021 .
  31. ^ Mazzola, Guerino; Milmeister, Gérard; Weissmann, Jody (21 de octubre de 2004). Matemáticas integrales para científicos informáticos 2. Springer Science & Business Media. pág. 323. ISBN 978-3-540-20861-7.
  32. ^ ab Sturm, Oliver (11 de abril de 2011). Programación funcional en C#: técnicas clásicas de programación para proyectos modernos. John Wiley and Sons. pág. 91. ISBN 978-0-470-74458-1.
  33. ^ Marlow, Simon. "¿Por qué no puedo obtener un seguimiento de la pila?". Taller de implementadores de Haskell 2012. Consultado el 25 de agosto de 2021 .
  34. ^ Nilsson, Henrik (1999). "Tracing piece by piece: Affordable debugging for lazy functional language" (Rastreo pieza por pieza: depuración asequible para lenguajes funcionales perezosos). Actas de la cuarta conferencia internacional ACM SIGPLAN sobre programación funcional . pp. 36–47. CiteSeerX 10.1.1.451.6513 . doi :10.1145/317636.317782. ISBN .  1581131119. Número de identificación del sujeto  13954359.
  35. ^ "Abrir parámetros de matriz". www.freepascal.org . Consultado el 20 de enero de 2024 .
  36. ^ abcd "¡Maldita sea, Java es un código de paso por valor!". 16 de mayo de 2001. Consultado el 24 de diciembre de 2016 .
  37. ^ Coenen, Frans. "PASO DE PARÁMETROS". cgi.csc.liv.ac.uk. ​Consultado el 22 de enero de 2024 .
  38. ^ "Llamada por referencia, problemas de alias" (PDF) . Curso MPRI 2-36-1: Prueba de programa (Apuntes de clase) . pág. 53.
  39. ^ Manual de referencia del lenguaje Ada 2022 (PDF) . 13 de octubre de 2023. pág. 215.
  40. ^ Barnes, John (2013). Fundamento de Ada 2012: el lenguaje, las bibliotecas estándar (PDF) . Heidelberg: Springer. págs. 15-16,87-88. ISBN. 978-3-642-45210-9.
  41. ^ Thurlow, Robert (mayo de 2009). "RPC: Remote Procedure Call Protocol Specification Version 2" (Especificación del protocolo de llamada a procedimiento remoto, versión 2). tools.ietf.org . IETF . Consultado el 7 de abril de 2018 .
  42. ^ Lundh, Fredrik. "Llamada por objeto". Effbot.org . Archivado desde el original el 2011-05-19 . Consultado el 2011-05-19 .
  43. ^ Jones, Rhys Price (2010). "¿Es Scheme una llamada por valor?". CS 145 Lenguajes de programación Laboratorio 9: Paso de parámetros . Universidad George Washington. Archivado desde el original el 16 de octubre de 2014. Consultado el 20 de enero de 2024 .
  44. ^ "Página del manual Procedimientos de la biblioteca Tcl - Tcl_Obj". www.tcl.tk .
  45. ^ "CA1021: Evitar parámetros de salida". Microsoft. 15 de noviembre de 2016.
  46. ^ Leo, Ray (noviembre de 1996). Little C++ (Made Easy). LeoSudo Inc., págs. 79-80. ISBN 978-0-9654634-1-6.
  47. ^ Dandamudi, Sivarama P. (15 de julio de 2005). Guía para la programación en lenguaje ensamblador en Linux. Springer Science & Business Media. pág. 232. ISBN 978-0-387-25897-3.
  48. ^ Srivastava, SK; Srivastava, Deepali (6 de junio de 2018). C in Depth. BPB Publications. pág. 206. ISBN 978-93-87284-94-4.
  49. ^ "Variables mutables y tipos de referencia". okmij.org . Consultado el 20 de enero de 2024 .
  50. ^ Vermeir, Dirk (28 de junio de 2011). Programación multiparadigma con C++. Springer Science & Business Media. pp. 10-11. ISBN 978-1-4471-0311-0.
  51. ^ McCollin, Thomas Gwynfryn; Morell, Tobias. "Un juego de paradigmas: un estudio de usabilidad de modismos funcionales en la programación de juegos" (PDF) . Universidad de Aalborg. pág. 6. Consultado el 11 de enero de 2022 .
  52. ^ ab Schauser, Klaus E.; Goldstein, Seth C. (1995). "¿Cuánta falta de rigurosidad requieren los programas indulgentes?" (PDF) . Actas de la séptima conferencia internacional sobre lenguajes de programación funcional y arquitectura informática - FPCA '95 . págs. 216–225. doi :10.1145/224164.224208. ISBN . 0897917197. S2CID  2045943 . Consultado el 7 de enero de 2022 .
  53. ^ Ennals, Robert; Jones, Simon Peyton (agosto de 2003). "Evaluación optimista: una estrategia de evaluación rápida para programas no estrictos".

Lectura adicional

  • Baker-Finch, Clem; King, David; Hall, Jon; Trinder, Phil (1999-03-10). "Una semántica operacional para llamadas paralelas por necesidad" (ps) . Informe de investigación . 99 (1). Facultad de Matemáticas y Computación, The Open University.
  • Ennals, Robert; Peyton Jones, Simon (2003). Optimistic Evaluation: A Fast Evaluation Strategy for Non-Strict Programs (PDF) . Conferencia internacional sobre programación funcional. ACM Press.
  • Ludäscher, Bertram (24 de enero de 2001). "Apuntes de clase de CSE 130". CSE 130: Lenguajes de programación: principios y paradigmas .
  • Pierce, Benjamin C. (2002). Tipos y lenguajes de programación . MIT Press . ISBN 0-262-16209-1.
  • Sestoft, Peter (2002). Mogensen, T; Schmidt, D; Sudborough, IH (eds.). Demostración de la reducción del cálculo lambda (PDF) . Apuntes de clase en informática. Vol. 2566. Springer-Verlag. págs. 420–435. ISBN 3-540-00326-6. {{cite book}}: |work=ignorado ( ayuda )
  • "Llamada por valor y llamada por referencia en programación en C". Explicación de la llamada por valor y la llamada por referencia en programación en C. Archivado desde el original el 21 de enero de 2013.
  • El visualizador interactivo en línea de geometría de interacción , que implementa una máquina basada en gráficos para varias estrategias de evaluación comunes.
Retrieved from "https://en.wikipedia.org/w/index.php?title=Evaluation_strategy&oldid=1247275836#Call_by_name"