En lenguajes FP, llamar a una función con los mismos parámetros una y otra vez devuelve el mismo resultado una y otra vez (es decir, transparencia referencial).
Pero una función como esta (pseudocódigo):
function f(a, b) {
return a + b + currentDateTime.seconds;
}
no va a devolver el mismo resultado para los mismos parámetros.
¿Cómo se manejan estos casos en FP?
¿Cómo se aplica la transparencia referencial? ¿O no es así y depende de los programadores que se comporten?
currentDateTime
desdef
, en idiomas que impongan transparencia referencial (como Haskell). Dejaré que alguien más proporcione una respuesta más detallada :) (pista:currentDateTime
hace IO, y esto se mostrará en su tipo)Respuestas:
a
yb
sonNumber
s, mientras quecurrentDateTime.seconds
devuelve unIO<Number>
. Esos tipos son incompatibles, no puede agregarlos juntos, por lo tanto, su función no está bien escrita y simplemente no se compilará. Al menos así es como se hace en lenguajes puros con un sistema de tipo estático, como Haskell. En lenguajes impuros como ML, Scala o F #, corresponde al programador garantizar la transparencia referencial y, por supuesto, en lenguajes de tipo dinámico como Clojure o Scheme, no existe un sistema de tipo estático para imponer la transparencia referencial.fuente
Trataré de ilustrar el enfoque de Haskell (no estoy seguro de que mi intuición sea 100% correcta ya que no soy un experto de Haskell, las correcciones son bienvenidas).
Su código se puede escribir en Haskell de la siguiente manera:
Entonces, ¿dónde está la transparencia referencial?
f
es una función que, dados dos enterosa
yb
, creará una acción, como puede ver por el tipo de retornoIO Integer
. Esta acción siempre será la misma, dados los dos enteros, por lo que la función que asigna un par de enteros a acciones IO es referencialmente transparente.Cuando se ejecuta esta acción, el valor entero que produce dependerá del tiempo actual de la CPU: la ejecución de acciones NO es una aplicación de función.
Resumiendo: en Haskell puedes usar funciones puras para construir y combinar acciones complejas (secuenciación, composición de acciones, etc.) de una manera referencialmente transparente. Nuevamente, tenga en cuenta que en el ejemplo anterior la función pura
f
no devuelve un entero: devuelve una acción.EDITAR
Algunos detalles más sobre la pregunta JohnDoDo.
¿Qué significa que "ejecutar acciones NO es una aplicación de función"?
Dados los conjuntos T1, T2, Tn, T, una función f es un mapeo (relación) que se asocia a cada tupla en T1 x T2 x ... x Tn un valor en T. Entonces la aplicación de función produce un valor de salida dados algunos valores de entrada . Con este mecanismo, puede construir expresiones que evalúen valores, por ejemplo, el valor
10
es el resultado de evaluar la expresión4 + 6
. Tenga en cuenta que, al asignar valores a valores de esta manera, no está realizando ningún tipo de entrada / salida.En Haskell, las acciones son valores de tipos especiales que pueden construirse evaluando expresiones que contienen funciones puras apropiadas que funcionan con acciones. De esta manera, un programa Haskell es una acción compuesta que se obtiene al evaluar la
main
función. Esta acción principal tiene tipoIO ()
.Una vez que se ha definido esta acción compuesta, se utiliza otro mecanismo (no aplicación de función) para invocar / ejecutar la acción (ver, por ejemplo, aquí ). Toda la ejecución del programa es el resultado de invocar la acción principal que a su vez puede invocar sub-acciones. Este mecanismo de invocación (cuyos detalles internos no conozco) se encarga de realizar todas las llamadas IO necesarias, posiblemente accediendo a la terminal, el disco, la red, etc.
Volviendo al ejemplo. La función
f
anterior no devuelve un entero y no puede escribir una función que realice IO y devuelva un entero al mismo tiempo: debe elegir uno de los dos.Lo que puede hacer es incrustar la acción devuelta
f 2 3
en una acción más compleja. Por ejemplo, si desea imprimir el número entero producido por esa acción, puede escribir:La
do
notación indica que la acción devuelta por la función principal se obtiene mediante una composición secuencial de dos acciones más pequeñas, y lax <-
notación indica que el valor producido en la primera acción debe pasarse a la segunda acción.En la segunda acción
el nombre
x
está vinculado al entero producido al ejecutar la acciónUn punto importante es que el número entero que se produce cuando se invoca la primera acción solo puede vivir dentro de las acciones IO: se puede pasar de una acción IO a la siguiente pero no se puede extraer como un valor entero simple.
Compare la
main
función anterior con esta:En este caso, solo hay una acción, a saber
putStrLn (show y)
, yy
está vinculada al resultado de aplicar la función pura+
. También podríamos definir esta acción principal de la siguiente manera:Entonces, observe la sintaxis diferente
Resumen
fuente
executing actions is NOT function application
frase? En mi ejemplo, tenía la intención de devolver un número entero. ¿Qué sucede si un retorno es un entero?f
no puede tener tipoIO Integer
(eso es una acción, no un número entero). Pero entonces, no puede llamar a la "fecha actual", que tiene tipoIO Integer
.Show
disponible. Pero puede agregar fácilmente uno, en cuyo caso el código se compilará y se ejecutará bien. No hay nada especial en lasIO
acciones con respecto ashow
.El enfoque habitual es permitir que el compilador rastree si una función es pura a través de todo el gráfico de llamadas, y rechazar el código que declara funciones como puras que hacen cosas impuras (donde "llamar a una función impura" también es una cosa impura).
Haskell hace esto haciendo todo puro en el lenguaje mismo; todo lo impuro se ejecuta en tiempo de ejecución, no el lenguaje en sí. El lenguaje simplemente construye acciones IO usando funciones puras. El tiempo de ejecución luego encuentra la función pura llamada
main
desde elMain
módulo designado , la evalúa y ejecuta la acción resultante (impura).Otros lenguajes son más pragmáticos al respecto; Un enfoque común es agregar sintaxis para marcar funciones 'puras' y prohibir cualquier acción impura (actualizaciones variables, llamadas a funciones impuras, construcciones de E / S) dentro de dichas funciones.
En su ejemplo,
currentDateTime
es una función impura (o algo que se comporta como tal), por lo que está prohibido llamarlo dentro de un bloque puro y provocaría un error del compilador. En Haskell, su función se vería así:Si trató de hacer esto en una función que no es IO, así:
... entonces el compilador le dirá que sus tipos no se desprotegen,
getCurrentTime
es de tipoIO Time
, noTime
, perotimeSeconds
esperaTime
. En otras palabras, Haskell aprovecha su sistema de tipos para modelar (y hacer cumplir) la pureza.fuente