Funciones puras: ¿"Sin efectos secundarios" implica "Siempre la misma salida, dada la misma entrada"?

84

Las dos condiciones que definen una función pureson las siguientes:

  1. Sin efectos secundarios (es decir, solo se permiten cambios en el alcance local)
  2. 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?

Magnus
fuente
3
Sus premisas están mal especificadas. La "entrada" es demasiado amplia. Se puede pensar que dos funciones tienen tipos de entrada. Sus argumentos, y "ambientales" / "contextuales". Se podría pensar que una función que devuelve la hora del sistema es pura (aunque obviamente no lo es) si no distingue entre estos dos tipos de entrada.
Alexander - Reincorpora a Monica
4
@Alexander: En el contexto de "función pura", "entrada" se entiende comúnmente como los parámetros / argumentos que se pasan explícitamente (por cualquier mecanismo que utilice el lenguaje de programación). Eso es parte de la definición de "función pura". Pero tienes razón, es importante tener en cuenta la definición.
sleske
3
Contraejemplo trivial: devuelve el valor de una variable global. Sin efectos secundarios (¡lo global solo se lee!), Pero aún así resultados potencialmente diferentes cada vez. (Si no le gustan los globales, devuelva la dirección de una variable local que depende de la pila de llamadas en tiempo de ejecución).
Peter - Reincorpora a Monica
2
Necesita ampliar su definición de "efectos secundarios"; usted dice que un método puro no produce efectos secundarios, pero también debe tener en cuenta que un método puro no consume efectos secundarios producidos en otros lugares.
Eric Lippert
2
@sleske Quizás se entienda comúnmente, pero la falta de esa distinción es la causa exacta de la confusión de OP.
Alexander - Reincorpora a Monica

Respuestas:

114

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:

  • la evaluación de resultados no debe tener efectos sobre el estado secundario
  • 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 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".

Bergi
fuente
1
Gracias Bergi. Por alguna razón, pensé que los efectos secundarios incluían la lectura de variables fuera del ámbito local, pero supongo que es solo un efecto secundario si escribe tales variables externas.
Magnus
17
Si prompt("you choose")no tiene efectos secundarios, debemos dar un paso atrás y aclarar el significado de los efectos secundarios.
Holger
1
@Magnus Sí, eso es exactamente lo que significa efecto . Intentaré aclarar en mi respuesta también, no esperaba tanta atención y quiero que la respuesta sea digna de docenas de votos :-)
Bergi
2
Bueno, por lo que sabes, Math.random () devuelve un diodo térmico. En realidad, no está especificado para usar un RNG incorrecto.
Joshua
1
De las dos condiciones, he oído que la primera se llama "efectos", mientras que la última se llama "coefectos". Ambos son "efectos secundarios" e impuros. f (coefectos, entrada) -> efectos, salida Los coefectos son entradas que provienen de los cambios en el entorno más amplio, los efectos son resultados que cambian el entorno más amplio. El reencuadre de Elm y Clojurescrips funciona con este modelo, por ejemplo.
30

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 printffueran 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 printfdevuelve el número de caracteres escritos, en este caso 5.

Se vuelve aún más obvio con las voidfunciones. Si tengo una función void foo, entonces

foo(bar, baz, quux);

debería ser lo mismo que

;

Es decir, dado que foono devuelve nada, debería poder reemplazarlo con nada sin cambiar el significado del programa.

Está claro, entonces, que ni printfni fooson referencialmente transparentes, por lo que ninguno de ellos es puro. De hecho, una voidfunció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 memomatriz. 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.

Jörg W Mittag
fuente
2
Esa impureza afecta a todo el programa una vez que tiene concurrencia.
R .. GitHub DEJA DE AYUDAR A ICE
@R .. ¿Puedes pensar en una forma en que la concurrencia podría hacer que la función de Fibonacci descrita sea externamente impura? No puedo. Escribir en memo[n]es idempotente y no leer de él simplemente desperdicia ciclos de CPU.
Brilliand
Estoy de acuerdo con los dos. La impureza puede generar problemas de simultaneidad, pero no es así en este caso específico.
Jörg W Mittag
@R .. No es difícil imaginar una versión compatible con la concurrencia.
user253751
1
@Brilliand Por ejemplo, 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.
user253751
12

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)con 6cualquier parte de nuestro programa y nada cambiará.

Por el contrario, no podemos reemplazar addOneAndLog(x)con el valor 6en 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 .

La Luz Interior
fuente
"Me parece que la segunda condición que ha descrito es una restricción más débil que la primera". No, las dos condiciones son lógicamente independientes.
sleske
@sleske estás equivocado. He proporcionado definiciones claras para los términos puro y efecto secundario. Dentro de estas restricciones, no hay nada que una función sin efectos secundarios, además de devolver la misma salida para una entrada determinada. Sin embargo, he proporcionado ejemplos en los que la segunda condición se puede satisfacer sin la primera. El concepto fundamental para entender la noción de pureza es la transparencia referencial.
TheInnerLight
Pequeño error tipográfico: no hay nada que una función sin efectos secundarios pueda hacer además de devolver la misma salida para una entrada determinada.
TheInnerLight
¿Qué tal algo como devolver la hora actual? Eso no tiene efectos secundarios, pero devuelve una salida diferente para la misma entrada. O más generalmente, cualquier función en la que el resultado dependa no solo de los parámetros de entrada, sino también de una variable global (modificable).
sleske
2
Parece que está utilizando una definición de "efecto secundario" diferente a la que se utiliza habitualmente. Un efecto secundario se define comúnmente como "un efecto observable además de devolver un valor" o un "cambio de estado observable"; consulte, por ejemplo , Wikipedia , este artículo sobre ingeniería de software.SE . Tiene toda la razón en que 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.
sleske
7

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.

usuario3340459
fuente
3
Según yo, estos "aleatoriedad desde fuera del sistema" son una forma de efecto secundario. Las funciones con estos comportamientos no son "puras".
Joseph M. Dion
2

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 una Trueconstante.

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: tcontiene 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.

AleatorioB
fuente
2

Si la primera condición es siempre verdadera, ¿hay ocasiones en las que la segunda condición no es verdadera?

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 Sumcon 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 .

rahulaga_dev
fuente
Pero en este caso no se verifica la primera condición: escribir en la consola se considera un efecto secundario, ya que cambia el estado de la propia máquina.
Pierna derecha
@ Rightleg gracias por señalarlo. De alguna manera entendí mal OP totalmente de otra manera. respuesta corregida.
rahulaga_dev
2
¿No cambia el estado del generador aleatorio?
Eric Duminil
1
Generar un número aleatorio es en sí mismo un efecto secundario, a menos que el estado del generador de números aleatorios se proporcione explícitamente, lo que haría que la función satisfaga la condición 2.
TheInnerLight
1
rndno 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 el Randomconstructor use la hora actual como valor semilla significa que hay "entradas" distintas de ay b.
Sneftel