Técnicamente, cualquier programa que ejecute en una computadora es impuro porque eventualmente se compila en instrucciones como "mover este valor a eax" y "agregar este valor al contenido de eax", que son impuros. Eso no es muy útil.
En cambio, pensamos en la pureza usando cajas negras . Si algún código siempre produce las mismas salidas cuando se le dan las mismas entradas, entonces se considera puro. Según esta definición, la siguiente función también es pura, aunque internamente utiliza una tabla de notas impura.
const fib = (() => {
const memo = [0, 1];
return n => {
if (n >= memo.length) memo[n] = fib(n - 1) + fib(n - 2);
return memo[n];
};
})();
console.log(fib(100));
No nos interesan las partes internas porque estamos utilizando una metodología de caja negra para verificar la pureza. Del mismo modo, no nos importa que todo el código se convierta finalmente en instrucciones de máquina impuras porque estamos pensando en la pureza utilizando una metodología de recuadro negro. Lo interno no es importante.
Ahora, considere la siguiente función.
const greet = name => {
console.log("Hello %s!", name);
};
greet("World");
greet("Snowman");
¿Es la greetfunción pura o impura? Según nuestra metodología de recuadro negro, si le damos la misma entrada (por ejemplo World), siempre imprime la misma salida en la pantalla (es decir Hello World!). En ese sentido, ¿no es puro? No, no es. La razón por la que no es pura es porque consideramos que imprimir algo en la pantalla es un efecto secundario. Si nuestra caja negra produce efectos secundarios, entonces no es pura.
¿Qué es un efecto secundario? Aquí es donde el concepto de transparencia referencial es útil. Si una función es referencialmente transparente, siempre podemos reemplazar las aplicaciones de esa función con sus resultados. Tenga en cuenta que esto no es lo mismo que la función en línea .
En la función en línea, reemplazamos las aplicaciones de una función con el cuerpo de la función sin alterar la semántica del programa. Sin embargo, una función referencialmente transparente siempre se puede reemplazar con su valor de retorno sin alterar la semántica del programa. Considere el siguiente ejemplo.
console.log("Hello %s!", "World");
console.log("Hello %s!", "Snowman");
Aquí, subrayamos la definición greety no cambió la semántica del programa.
Ahora, considere el siguiente programa.
Aquí, reemplazamos las aplicaciones de la greetfunción con sus valores de retorno y cambió la semántica del programa. Ya no estamos imprimiendo saludos a la pantalla. Esa es la razón por la cual la impresión se considera un efecto secundario, y es por eso que la greetfunción es impura. No es referencialmente transparente.
Ahora, consideremos otro ejemplo. Considere el siguiente programa.
const main = async () => {
const response = await fetch("https://time.akamai.com/");
const serverTime = 1000 * await response.json();
const timeDiff = time => time - serverTime;
console.log("%d ms", timeDiff(Date.now()));
};
main();
Claramente, la mainfunción es impura. Sin embargo, es eltimeDiff función pura o impura? Aunque depende de serverTimecuál provenga de una llamada de red impura, sigue siendo referencialmente transparente porque devuelve las mismas salidas para las mismas entradas y porque no tiene ningún efecto secundario.
zerkms probablemente no estará de acuerdo conmigo en este punto. En su respuesta , dijo que eldollarToEuro función en el siguiente ejemplo es impura porque "depende de la IO transitivamente".
const exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x, exchangeRate) => {
return x * exchangeRate;
};
Tengo que estar en desacuerdo con él porque el hecho de que exchangeRate provenga de una base de datos es irrelevante. Es un detalle interno y nuestra metodología de recuadro negro para determinar la pureza de una función no se preocupa por los detalles internos.
En lenguajes puramente funcionales como Haskell, tenemos una escotilla de escape para ejecutar efectos de E / S arbitrarios. Se llamaunsafePerformIO , y como su nombre lo indica, si no lo usa correctamente, entonces no es seguro porque podría romper la transparencia referencial. Sin embargo, si sabes lo que estás haciendo, entonces es perfectamente seguro de usar.
Generalmente se usa para cargar datos de archivos de configuración cerca del comienzo del programa. Cargar datos de archivos de configuración es una operación de IO impura. Sin embargo, no queremos ser agobiados al pasar los datos como entradas a cada función. Por lo tanto, si usamosunsafePerformIO , podemos cargar los datos en el nivel superior y todas nuestras funciones puras pueden depender de los datos de configuración global inmutables.
Tenga en cuenta que el hecho de que una función dependa de algunos datos cargados desde un archivo de configuración, una base de datos o una llamada de red, no significa que la función sea impura.
Sin embargo, consideremos su ejemplo original que tiene una semántica diferente.
let exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x) => {
return x * exchangeRate;
};
dollarToEuro(100) //90 today
dollarToEuro(100) //something else tomorrow
Aquí, supongo que debido a que exchangeRateno está definido como const, se modificará mientras se ejecuta el programa. Si ese es el caso, entonces dollarToEurodefinitivamente es una función impura porque cuando exchangeRatese modifica, se romperá la transparencia referencial.
Sin embargo, si la exchangeRatevariable no se modifica y nunca se modificará en el futuro (es decir, si es un valor constante), aunque se defina como let, no se romperá la transparencia referencial. En ese caso, dollarToEuroes de hecho una función pura.
Tenga en cuenta que el valor de exchangeRatepuede cambiar cada vez que ejecute el programa nuevamente y no romperá la transparencia referencial. Solo rompe la transparencia referencial si cambia mientras el programa se está ejecutando.
Por ejemplo, si ejecuta mi timeDiffejemplo varias veces, obtendrá diferentes valores serverTimey, por lo tanto, diferentes resultados. Sin embargo, debido a que el valor de serverTimenunca cambia mientras se ejecuta el programa, la timeDifffunción es pura.
function myNumber(n) { this.n = n; }; myNumber.prototype.valueOf = function() { console.log('impure'); return this.n; }; const n = new myNumber(42); add(n, 1);