Las dos condiciones que definen una función pure
son las siguientes:
- Sin efectos secundarios (es decir, solo se permiten cambios en el alcance local)
- Devuelve siempre la misma salida, dada la misma entrada
Si la primera condición es siempre verdadera, ¿hay ocasiones en las que la segunda condición no es verdadera?
Es decir, ¿realmente solo es necesario con la primera condición?
Respuestas:
Aquí hay algunos contraejemplos que no cambian el alcance externo pero 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(); }
(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 en las dos condiciones para la pureza como complementarias:
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 y también implican escritura, incluso si su propósito principal es acceder a un valor. Ejemplos de eso son generar un número pseudoaleatorio que modifica el estado interno del generador, leer de un flujo de entrada que avanza la posición de lectura o leer de un sensor externo que involucra un comando de "tomar medida".
fuente
prompt("you choose")
no tiene efectos secundarios, debemos dar un paso atrás y aclarar el significado de los efectos secundarios.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 .
Transparencia referencial , 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 C's
printf
fueran referencialmente transparentes, estos dos programas deberían tener el mismo significado:printf("Hello");
y
5;
y todos los siguientes programas deben tener el mismo significado:
5 + 5; printf("Hello") + 5; printf("Hello") + printf("Hello");
Porque
printf
devuelve el número de caracteres escritos, en este caso 5.Se vuelve aún más obvio con las
void
funciones. Si tengo una funciónvoid foo
, entoncesdebería ser lo mismo que
Es decir, dado que
foo
no devuelve nada, debería poder reemplazarlo con nada sin cambiar el significado del programa.Está claro, entonces, que ni
printf
nifoo
son referencialmente transparentes, por lo que ninguno de ellos es puro. De hecho, unavoid
función nunca puede ser referencialmente transparente, a menos que no sea una operación.Encuentro esta definición mucho más fácil de manejar que la que usted dio. También le permite aplicarlo en la 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 componen 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
memo
matriz. Sin embargo, también podemos mirar la función y podemos ver que es referencialmente transparente y, por lo tanto, pura. A esto a veces se le llama pureza externa , es decir, una función que parece pura para el mundo exterior, pero que se implementa como impura internamente.Estas funciones siguen siendo útiles, porque mientras la impureza infecta todo lo que la rodea, la interfaz pura externa construye una especie de "barrera de pureza", donde la impureza solo infecta las tres líneas de la función, pero no se filtra al resto del programa. . Estas tres líneas son mucho más fáciles de analizar para determinar si son correctas que el programa completo.
fuente
memo[n]
es idempotente y no leer de él simplemente desperdicia ciclos de CPU.memo[n] = ...
primero puede crear una entrada de diccionario y luego almacenar el valor en ella. Esto deja una ventana durante la cual otro hilo podría ver una entrada no inicializada.Me parece que la segunda condición que ha descrito es una restricción más débil que la primera.
Déjame darte un ejemplo, supongamos que tienes una función para agregar una que también 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 de 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)
con6
cualquier parte de nuestro programa y nada cambiará.Por el contrario, no podemos reemplazar
addOneAndLog(x)
con el valor6
en cualquier lugar de nuestro programa sin cambiar el comportamiento porque la primera expresión da como resultado que se escriba algo en la consola mientras que la segunda no.Consideramos cualquier comportamiento adicional que se
addOneAndLog(x)
realice además de devolver la salida como un efecto secundario .fuente
Date.now()
no es puro / referencialmente transparente, pero no porque tenga efectos secundarios, sino porque su resultado depende de algo más que su entrada.Podría haber una fuente de aleatoriedad externa al sistema. Suponga que parte de su cálculo incluye la temperatura ambiente. Luego, la ejecución de la función producirá resultados diferentes cada vez, dependiendo del elemento externo aleatorio de la temperatura ambiente. El estado no cambia al ejecutar el programa.
Todo en lo que puedo pensar, de todos modos.
fuente
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.). Ésta 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 humanos como creadores de todo el sistema y poseedores de 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 una propiedad referencialmente transparente y se puede sustituir por unaTrue
constante.Entonces, para evitar tal inexactitud, debemos distinguir funciones matemáticas y "funciones" en lenguajes de programación. Las funciones en Haskell son siempre impuras y la definición de pureza relacionada con ellas siempre es muy condicional: se ejecutan en hardware real con efectos secundarios y propiedades físicas reales, lo cual 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 también son puras: cada función que tiene
t
(tiempo) como parámetro puede ser impura:t
contiene 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 sobre los valores reales, puede sea incluso un ruido.fuente
si
Considere el fragmento de código simple a continuación
public int Sum(int a, int b) { Random rnd = new Random(); return rnd.Next(1, 10); }
Este código devolverá una 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 mencionaste cuando se combinan 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 .fuente
rnd
no escapa a la función, por lo que el hecho de que su estado cambie no importa para la pureza de la función, pero el hecho de que elRandom
constructor use la hora actual como valor semilla significa que hay "entradas" distintas dea
yb
.