¿Cómo se llama una función donde la misma entrada siempre devolverá la misma salida, pero también tiene efectos secundarios?

43

Digamos que tenemos una función pura normal como

function add(a, b) {
  return a + b
}

Y luego lo modificamos de modo que tenga un efecto secundario

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

Por lo que sé, no se considera una función pura porque a menudo escucho que las personas llaman a las funciones puras "funciones sin efectos secundarios". Sin embargo, se comporta como una función pura en cuanto al hecho de que devolverá la misma salida para las mismas entradas.

¿Hay un nombre diferente para este tipo de función, no tiene nombre o sigue siendo puro y estoy equivocado acerca de la definición de pureza?

m0meni
fuente
9191
"No es una función pura".
Ross Patterson
2
@RossPatterson, eso es lo que también pensé, pero al preguntar, aprendí sobre la transparencia referencial, así que me alegro de no guardarlo para mí.
m0meni
99
Si writeToDatabasefalla, podría desencadenar una excepción, haciendo que su segunda addfunción produzca una excepción a veces incluso si se llama con los mismos argumentos que antes no tenían problemas ... la mayoría de las veces tener efectos secundarios introduce este tipo de condiciones relacionadas con errores que se rompen "pureza de entrada-salida".
Bakuriu
25
Algo que siempre da la misma salida para una entrada dada se llama determinista .
njzk2
2
@ njzk2: Cierto, pero también es apátrida . Una función determinista con estado puede no dar la misma salida para cada entrada. Ejemplo: F(x)se define para devolver truesi se llama con el mismo argumento que la llamada anterior. Claramente con la secuencia {1,2,2} => {undefined, false, true}esto es determinista, pero da diferentes resultados para F(2).
MSalters

Respuestas:

85

No estoy seguro de las definiciones universales de pureza, pero desde el punto de vista de Haskell (un lenguaje donde los programadores tienden a preocuparse por cosas como la pureza y la transparencia referencial), solo la primera de sus funciones es "pura". La segunda versión de addno es pura . Entonces, en respuesta a su pregunta, lo llamaría "impuro";)

Según esta definición, una función pura es una función que:

  1. Solo depende de su entrada. Es decir, dada la misma entrada, siempre devolverá la misma salida.
  2. Es referencialmente transparente: la función se puede reemplazar libremente por su valor y el "comportamiento" del programa no cambiará.

Con esta definición, está claro que su segunda función no puede considerarse pura, ya que infringe la regla 2. Es decir, los siguientes dos programas NO son equivalentes:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

y

function g(a, b) {
    c = add(a, b);
    return c + c;
}

Esto se debe a que aunque ambas funciones devolverán el mismo valor, la función fescribirá en la base de datos dos veces, ¡pero gescribirá una vez! Es muy probable que las escrituras en la base de datos sean parte del comportamiento observable de su programa, en cuyo caso le he mostrado que su segunda versión addno es "pura".

Si las escrituras en la base de datos no son una parte observable del comportamiento de su programa, entonces ambas versiones de addpueden considerarse equivalentes y puras. Pero no puedo pensar en un escenario en el que escribir en la base de datos no importe. ¡Incluso la tala importa!

Andres F.
fuente
1
¿No es "solo depende de su entrada" redundante dada la transparencia referencial? ¿Qué implicaría que RT es sinónimo de pureza? (Me estoy confundiendo más sobre esto mientras más fuentes busco)
Ixrec
Estoy de acuerdo, es confuso. Solo puedo pensar en ejemplos artificiales. Decir f(x)depende no solo de x, sino también de alguna variable global externa y. Luego, si ftiene la propiedad de RT, puede intercambiar libremente todas sus ocurrencias con su valor de retorno siempre que no lo toque y . Sí, mi ejemplo es dudoso. Pero lo importante es: si fescribe en la base de datos (o escribe en un registro) pierde la propiedad de RT: ahora no importa si deja yintacto global , sabe el significado de su programa cambia dependiendo de si realmente llamar fo simplemente usar su valor de retorno.
Andres F.
Humph Digamos que tenemos una función que es pura excepto por los efectos secundarios y también se garantiza que tenga ese comportamiento donde sus dos muestras son equivalentes. (De hecho, este caso surgió, así que no es hipotético). Creo que aún no hemos terminado.
Joshua
2
Yo diría que la segunda función también podría romper la regla # 1. Dependiendo del idioma y de las prácticas de manejo de errores de la API de la base de datos en uso, es posible que la función no devuelva nada si la base de datos no está disponible o la escritura de db falla por alguna razón.
Zach Lipton
1
Desde que se mencionó a Haskell: en Haskell, agregar un efecto secundario como ese requiere cambiar la firma de la función . (piense en ello como dar a la base de datos original como una entrada adicional y obtener la base de datos modificada como un valor de retorno adicional de la función). De hecho, es posible modelar efectos secundarios con bastante elegancia en el sistema de tipos de esa manera, es solo que los idiomas principales de hoy en día no se preocupan lo suficiente por los efectos secundarios y la pureza para hacer esto.
ComicSansMS
19

¿Cómo se llama una función [para la cual] la misma entrada siempre devolverá la misma salida, pero también tiene efectos secundarios?

Tal función se llama

determinista

Un algoritmo cuyo comportamiento puede predecirse completamente a partir de la entrada.

termwiki.com

Con respecto al estado:

Según la definición de una función que utilice, una función no tiene estado. Si vienes del mundo orientado a objetos, recuerda que x.f(y)es un método. Como una función se vería así f(x,y). Y si le gustan los cierres con alcance léxico cerrado, recuerde que el estado inmutable también podría ser parte de la expresión de funciones. Es solo un estado mutable que impactaría las funciones de naturaleza determinista. Entonces f (x) = x + 1 es determinista siempre que el 1 no cambie. No importa dónde esté almacenado el 1.

Sus funciones son ambas deterministas. Tu primero es también una función pura. Tu segundo no es puro.

Función pura

  1. La función siempre evalúa el mismo valor de resultado dados los mismos valores de argumento. El valor del resultado de la función no puede depender de ninguna información oculta o estado que pueda cambiar mientras se ejecuta la ejecución del programa o entre diferentes ejecuciones del programa, ni puede depender de ninguna entrada externa de los dispositivos de E / S.

  2. La evaluación del resultado no causa ningún efecto secundario o salida semánticamente observable, como la mutación de objetos mutables o la salida a dispositivos de E / S.

wikipedia.org

El punto 1 significa determinista . El punto 2 significa transparencia referencial . Juntos significan que una función pura solo permite que sus argumentos y su valor devuelto cambien. Nada más causa cambio. Nada más ha cambiado.

naranja confitada
fuente
-1. Escribir en la base de datos depende del estado externo que generalmente no se puede determinar mirando las entradas. La base de datos puede no estar disponible por varias razones y no es predecible si la operación tendrá éxito. Este no es un comportamiento determinista.
Frax
La memoria del sistema @Frax podría no estar disponible. La CPU podría no estar disponible. Ser determinista no garantiza el éxito. Garantiza que el comportamiento exitoso es predecible.
candied_orange
OOMing no es específico de ninguna función, es una categoría diferente de problema. Ahora, veamos el punto 1. de su definición de "función pura" (que de hecho significa "determinista"): "El valor del resultado de la función no puede depender de ninguna información oculta o estado que pueda cambiar mientras se ejecuta la ejecución del programa o entre diferentes ejecuciones de el programa , ni puede depender de ninguna entrada externa de dispositivos de E / S ". La base de datos es ese tipo de estado, por lo que la función de los OP claramente no cumple con esta condición, no es determinista.
Frax
@candied_orange Estaría de acuerdo si la escritura en la base de datos dependiera solo de las entradas. Pero es Math.random(). Entonces, no, a menos que supongamos un PRNG (en lugar de un RNG físico) Y consideremos que los PRNG indican parte de la entrada (que no es, la referencia está codificada), no es determinista.
marstato el
1
@candied_orange su cita de estados deterministas "un algoritmo cuyo comportamiento puede predecirse completamente a partir de la entrada". Escribirle a IO, para mí, es definitivamente un comportamiento, no un resultado.
marstato
9

Si no le importa el efecto secundario, entonces es referencialmente transparente. Por supuesto, es posible que no te importe, pero a alguien más le importa, por lo que la aplicabilidad del término depende del contexto.

No conozco un término general para precisamente las propiedades que describe, pero un subconjunto importante son aquellos que son idempotentes . En ciencias de la computación, un poco diferente a las matemáticas *, una función idempotente es aquella que puede repetirse con el mismo efecto; es decir, el resultado del efecto secundario neto de hacerlo muchas veces es el mismo que hacerlo una vez.

Entonces, si su efecto secundario fuera actualizar una base de datos con un cierto valor en una determinada fila, o crear un archivo con contenido exactamente consistente, entonces sería idempotente , pero si se agrega a la base de datos o se agrega a un archivo , entonces no lo haría.

Las combinaciones de funciones idempotentes pueden o no ser idempotentes en su conjunto.

* El uso de idempotente de manera diferente en ciencias de la computación que las matemáticas parece provenir de un uso incorrecto del término matemático que se adoptó porque el concepto es útil.

Jon Hanna
fuente
3
el término "referencialmente transparente" se define más estrictamente que si a "alguien le importa" o no. incluso si hacemos caso omiso de los problemas IO tales como problemas de conexión, falta de cadenas de conexión, los tiempos de espera, etc., entonces sigue siendo fácil demostrar que un programa que reemplaza (f x, f x)con let y = f x in (y, y)correrá en fuera de espacio en el disco excepciones doble de rápido se podría argumentar que estos son casos extremos que no le importan, pero con una definición tan difusa, podríamos llamarlo new Random().Next()referencialmente transparente porque, diablos, de todos modos no me importa qué número obtengo.
Sara
@kai: Dependiendo del contexto, puede ignorar los efectos secundarios. Por otro lado, el valor de retorno de una función como aleatorio no es un efecto secundario: es su efecto principal.
Giorgio
@Giorgio Random.Nexten .NET tiene efectos secundarios. Mucho más. Si puede Next, asígnelo a una variable y luego Nextvuelva a llamar y asígnelo a otra variable, lo más probable es que no sean iguales. ¿Por qué? Porque invocar Nextcambia algún estado interno oculto en el Randomobjeto. Este es el polo opuesto de la transparencia referencial. No entiendo su afirmación de que los "efectos principales" no pueden ser efectos secundarios. En el código imperativo es más común que no que el efecto principal sea un efecto secundario, porque los programas imperativos tienen estado por naturaleza.
Sara
3

No sé cómo se llaman tales funciones (o si incluso hay algún nombre sistemático), pero llamaría a una función que no es pura (ya que otras respuestas se acobardan) pero siempre devuelve el mismo resultado si se proporciona con los mismos parámetros. parámetros "(en comparación con la función de sus parámetros y algún otro estado). Lo llamaría simplemente función, pero desafortunadamente cuando decimos "función" en el contexto de la programación, queremos decir algo que no tiene que ser una función real.

user470365
fuente
1
¡Convenido! Es (informalmente) la definición matemática de "función", pero como usted dice, desafortunadamente "función" significa algo diferente en los lenguajes de programación, donde está más cerca de "un procedimiento paso a paso requerido para obtener un valor".
Andres F.
2

Básicamente depende de si te importa o no la impureza. Si la semántica de esta tabla es que no le importa cuántas entradas hay, entonces es puro. De lo contrario, no es puro.

O para decirlo de otra manera, está bien siempre que las optimizaciones basadas en la pureza no rompan la semántica del programa.

Un ejemplo más realista sería si intentara depurar esta función y agregara declaraciones de registro. Técnicamente, el registro es un efecto secundario. ¿Los registros lo hacen impuro? No.

DeadMG
fuente
Bueno, eso depende. Tal vez los registros lo hacen impuro, por ejemplo, si le importa cuántas veces y en qué momento, aparece "INFO f () llamado" en su registro. Lo que haces a menudo :)
Andres F.
8
-1 Los registros son importantes. En la mayoría de las plataformas, la salida de cualquier tipo sincroniza implícitamente el hilo de ejecución. El comportamiento de su programa depende de otras escrituras de hilos, de escritores de registros externos, a veces de lecturas de registros, del estado de los descriptores de archivos. Es tan puro como un cubo de tierra.
Basilevs
@AndresF. Bueno, probablemente no te importe la cantidad literal de veces. Probablemente solo le importe que se registre tantas veces como se llamó a la función.
DeadMG
@Basilevs El comportamiento de la función no depende en absoluto de ellos. Si falla la escritura del registro, simplemente continúe.
DeadMG
2
Se trata de si elige definir el registrador como parte del entorno de ejecución o no. Para otro ejemplo, ¿mi función pura sigue siendo pura si adjunto un depurador al proceso y establezco un punto de interrupción? Desde el punto de vista de la persona que usa el depurador, claramente la función tiene efectos secundarios, pero normalmente analizamos un programa con la convención de que esto "no cuenta". Lo mismo puede (pero no es necesario) para el registro utilizado para la depuración, lo que supongo es por qué el rastreo oculta su impureza. El registro de misión crítica, por ejemplo, para auditoría, es un efecto secundario significativo.
Steve Jessop
1

Diría que lo mejor que podemos preguntar no es cómo lo llamaríamos, sino cómo analizaríamos un código de este tipo. Y mi primera pregunta clave en dicho análisis sería:

  • ¿El efecto secundario depende del argumento de la función o del resultado del efecto secundario?
    • No: la "función efectiva" se puede refactorizar en una función pura, una acción efectiva y un mecanismo para combinarlas.
    • Sí: la "función efectiva" es una función que produce un resultado monádico.

Esto es sencillo de ilustrar en Haskell (y esta oración es solo medio chiste). Un ejemplo del caso "no" sería algo como esto:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

En este ejemplo, la acción que tomamos (imprimir la línea "I'm doubling some number") no tiene impacto en la relación entre xy el resultado. Esto significa que podemos refactorizarlo de esta manera (usando la Applicativeclase y su *>operador), lo que muestra que la función y el efecto son de hecho ortogonales:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

Entonces, en este caso, yo personalmente diría que es un caso en el que puedes factorizar una función pura. Gran parte de la programación de Haskell se trata de esto: aprender a factorizar las partes puras del código efectivo.

Un ejemplo del tipo "sí", donde las partes pura y efectiva no son ortogonales:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

Ahora, la cadena que imprime depende del valor de x. La función de la parte (multiplicar xpor dos), sin embargo, no depende de los efectos en absoluto, por lo que todavía puede factorizar un vistazo:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

Podría seguir deletreando otros ejemplos, pero espero que esto sea suficiente para ilustrar el punto con el que comencé: no lo "llamas" algo, analizas cómo se relacionan las partes pura y efectiva y las factorizas cuando es así. a su ventaja.

Esta es una de las razones por las que Haskell usa su Monadclase tan ampliamente. Las mónadas son (entre otras cosas) una herramienta para realizar este tipo de análisis y refactorización.

sacundim
fuente
-2

Las funciones que están destinadas a causar efectos secundarios a menudo se denominan efectivas . Ejemplo https://slpopejoy.github.io/posts/Effectful01.html

Ben Hutchison
fuente
Solo respondo para mencionar el término ampliamente reconocido efectivo y se vota abajo ... La ignorancia es una dicha, supongo. ..
Ben Hutchison
"eficaz" es una palabra que el autor de esa publicación optó por "tener efectos secundarios". Él mismo lo dice.
Robert Harvey
La función eficaz de Google revela muchas pruebas de que es un término ampliamente utilizado. La publicación del blog se dio como uno de los muchos ejemplos, no como la definición. En los círculos de programación funcional donde las funciones puras son las predeterminadas, existe la necesidad de un término positivo para describir deliberadamente las funciones de efectos secundarios. Es decir, más que la ausencia de pureza . Efectivo es ese término. Ahora, considérate educado.
Ben Hutchison
Meh
Robert Harvey