javascript - Funciones puras: ¿"Sin efectos secundarios" implica "Siempre la misma salida, dada la misma entrada"?
functional-programming language-lawyer (6)
Si la primera condición siempre es verdadera, ¿hay alguna vez que la segunda condición no sea verdadera?
Sí
Considere el siguiente fragmento de código simple
public int Sum(int a, int b) {
Random rnd = new Random();
return rnd.Next(1, 10);
}
Este código devolverá la salida aleatoria para el mismo conjunto de entradas dado, sin embargo, no tiene ningún efecto secundario.
El efecto general de los puntos # 1 y # 2 que mencionó cuando se combinaron significa:
En cualquier momento, si la función
Sum
con el mismo i / p se reemplaza con su resultado en un programa, el significado general del programa no cambia
.
Esto no es más que
transparencia referencial
.
Las dos condiciones que definen una función como
pure
son las siguientes:
- Sin efectos secundarios (es decir, solo se permiten cambios en el ámbito local)
- Siempre devuelve la misma salida, dada la misma entrada
Si la primera condición siempre es verdadera, ¿hay alguna vez que la segunda condición no sea verdadera?
Es decir, ¿es realmente solo necesario con la primera condición?
Aquí hay algunos contraejemplos que no cambian el alcance externo pero que aún se consideran impuros:
-
function a() { return Date.now(); }
-
function b() { return window.globalMutableVar; }
-
function c() { return document.getElementById("myInput").value; }
-
function d() { return Math.random(); }
function d() { return Math.random(); }
(lo que ciertamente cambia el PRNG, pero no se considera observable)
El acceso a variables no locales no constantes es suficiente para poder violar la segunda condición.
Siempre pienso que las dos condiciones para la pureza son complementarias:
- La evaluación de resultados no debe tener efectos en el estado lateral.
- El resultado de la evaluación no debe verse afectado por el estado lateral.
El término efecto secundario solo se refiere al primero, la función que modifica el estado no local. Sin embargo, a veces las operaciones de lectura también se consideran efectos secundarios: cuando son operaciones e implican también la escritura, incluso si su propósito principal es acceder a un valor. Algunos ejemplos son la generación de un número pseudoaleatorio que modifica el estado interno del generador, la lectura de un flujo de entrada que avanza la posición de lectura, o la lectura de un sensor externo que involucra un comando de "tomar medición".
El problema con las definiciones de PF es que son muy artificiales. Cada evaluación / cálculo tiene efectos secundarios en el evaluador. Es teóricamente cierto. Una negación de esto muestra solo que los apologistas de FP ignoran la filosofía y la lógica: una "evaluación" significa cambiar el estado de algún entorno inteligente (máquina, cerebro, etc.). Esta es la naturaleza del proceso de evaluación. Sin cambios - sin "cálculos". El efecto puede ser muy visible: calentar la CPU o su falla, apagar la placa base en caso de sobrecalentamiento, etc.
Cuando se habla de transparencia referencial, debe comprender que la información sobre dicha transparencia está disponible para el ser humano como creador de todo el sistema y titular de la información semántica y puede no estar disponible para el compilador.
Por ejemplo, una función puede leer algún recurso externo y tendrá una mónada IO en su firma, pero devolverá el mismo valor todo el tiempo (por ejemplo, el resultado de
current_year > 0
).
El compilador no sabe que la función devolverá siempre el mismo resultado, por lo que la función es impura pero tiene propiedad referencialmente transparente y se puede sustituir con la constante
True
.
Por lo tanto, para evitar tal inexactitud, debemos distinguir las funciones matemáticas y las "funciones" en los lenguajes de programación. Las funciones en Haskell son siempre impuras y la definición de pureza relacionada con ellas es siempre muy condicional: se ejecutan en hardware real con efectos secundarios reales y propiedades físicas, lo que es incorrecto para las funciones matemáticas. Esto significa que el ejemplo con la función "printf" es totalmente incorrecto.
Pero no todas las funciones matemáticas son puras también: cada función que tiene
t
(tiempo) como parámetro puede ser impura:
t
tiene todos los efectos y la naturaleza estocástica de la función: en el caso común, tiene una señal de entrada y no tiene idea de los valores reales, Puede ser incluso un ruido.
La forma "normal" de expresar lo que es una función pura , es en términos de transparencia referencial . Una función es pura si es referencialmente transparente .
La Transparencia de referencia , aproximadamente, significa que puede reemplazar la llamada a la función con su valor de retorno o viceversa en cualquier punto del programa, sin cambiar el significado del programa.
Entonces, por ejemplo, si el
printf
de C era referencialmente transparente, estos dos programas deberían tener el mismo significado:
printf("Hello");
y
5;
y todos los programas siguientes deberían tener el mismo significado:
5 + 5;
printf("Hello") + 5;
printf("Hello") + printf("Hello");
Debido a que
printf
devuelve el número de caracteres escritos, en este caso 5.
Se vuelve aún más obvio con las funciones de
void
.
Si tengo una función
void foo
, entonces
foo(bar, baz, quux);
debe ser el mismo que
;
Es decir, como
foo
no devuelve nada, debería poder reemplazarlo con nada sin cambiar el significado del programa.
Es claro, entonces, que ni
printf
ni
foo
son referencialmente transparentes, y por lo tanto ninguno de ellos es puro.
De hecho, una función de
void
nunca puede ser referencialmente transparente, a menos que sea una no-op.
Encuentro esta definición mucho más fácil de manejar como la que usted dio. También le permite aplicarlo a cualquier granularidad que desee: puede aplicarlo a expresiones individuales, a funciones, a programas completos. Te permite, por ejemplo, hablar de una función como esta:
func fib(n):
return memo[n] if memo.has_key?(n)
return 1 if n <= 1
return memo[n] = fib(n-1) + fib(n-2)
Podemos analizar las expresiones que conforman la función y concluir fácilmente que no son referencialmente transparentes y, por lo tanto, no son puras, ya que utilizan una estructura de datos mutable, es decir, la matriz de
memo
.
Sin embargo, también podemos ver la función y ver que
es
referencialmente transparente y, por lo tanto, puro.
Esto a veces se denomina
pureza externa
, es decir, una función que parece pura para el mundo exterior, pero que se implementa de manera interna e impura.
Estas funciones siguen siendo útiles, porque mientras que la impureza infecta todo lo que la rodea, la interfaz pura externa crea una especie de "barrera de pureza", donde la impureza solo infecta las tres líneas de la función, pero no se filtra hacia el resto del programa. . Estas tres líneas son mucho más fáciles de analizar para ver si están correctas que todo el programa.
Me parece que la segunda condición que ha descrito es una restricción más débil que la primera.
Permítame darle un ejemplo, suponga que tiene una función para agregar una que también se registra en la consola:
function addOneAndLog(x) {
console.log(x);
return x + 1;
}
Se cumple la segunda condición que proporcionó: esta función siempre devuelve la misma salida cuando se le da la misma entrada. Sin embargo, no es una función pura porque incluye el efecto secundario de iniciar sesión en la consola.
Una función pura es, estrictamente hablando, una función que satisface la propiedad de transparencia referencial . Esa es la propiedad que podemos reemplazar una aplicación de función con el valor que produce sin cambiar el comportamiento del programa.
Supongamos que tenemos una función que simplemente agrega:
function addOne(x) {
return x + 1;
}
Podemos reemplazar
addOne(5)
con
6
en cualquier lugar de nuestro programa y nada cambiará.
Por el contrario, no podemos reemplazar
addOneAndLog(x)
con el valor
6
en cualquier lugar de nuestro programa sin cambiar el comportamiento porque la primera expresión hace que algo se escriba en la consola, mientras que la segunda no.
Consideramos cualquiera de este comportamiento adicional que realiza
addOneAndLog(x)
además de devolver la salida como
efecto secundario
.
Podría haber una fuente de aleatoriedad desde fuera del sistema. Supongamos que parte de su cálculo incluye la temperatura de la habitación. Luego, ejecutar la función producirá resultados diferentes cada vez, dependiendo del elemento externo aleatorio de la temperatura ambiente. El estado no se cambia ejecutando el programa.
Todo lo que puedo pensar, de todos modos.