La transparencia referencial, referida a una función, indica que puede determinar el resultado de aplicar esa función solo observando los valores de sus argumentos. Puede escribir funciones referencialmente transparentes en cualquier lenguaje de programación, por ejemplo, Python, Scheme, Pascal, C.
Por otro lado, en la mayoría de los idiomas también puede escribir funciones transparentes no referenciales. Por ejemplo, esta función de Python:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
no es referencialmente transparente, de hecho llama
foo(x) + foo(x)
y
2 * foo(x)
producirá diferentes valores, para cualquier argumento x
. La razón de esto es que la función usa y modifica una variable global, por lo tanto, el resultado de cada invocación depende de este estado cambiante, y no solo del argumento de la función.
Haskell, un lenguaje puramente funcional, separa estrictamente la evaluación de expresiones en la que se aplican funciones puras y que siempre es referencialmente transparente, de la ejecución de la acción (procesamiento de valores especiales), que no es referencialmente transparente, es decir, ejecutar la misma acción puede tener cada vez que Resultado diferente.
Entonces, para cualquier función de Haskell
f :: Int -> Int
y cualquier número entero x
, siempre es cierto que
2 * (f x) == (f x) + (f x)
Un ejemplo de una acción es el resultado de la función de biblioteca getLine
:
getLine :: IO String
Como resultado de la evaluación de expresiones, esta función (en realidad una constante) produce en primer lugar un valor puro de tipo IO String
. Los valores de este tipo son valores como cualquier otro: puede pasarlos, colocarlos en estructuras de datos, componerlos usando funciones especiales, etc. Por ejemplo, puede hacer una lista de acciones de esta manera:
[getLine, getLine] :: [IO String]
Las acciones son especiales, ya que puede indicarle al tiempo de ejecución de Haskell que las ejecute escribiendo:
main = <some action>
En este caso, cuando se inicia su programa Haskell, el tiempo de ejecución recorre la acción vinculada main
y la ejecuta , posiblemente produciendo efectos secundarios. Por lo tanto, la ejecución de la acción no es referencialmente transparente porque ejecutar la misma acción dos veces puede producir resultados diferentes dependiendo de lo que el tiempo de ejecución obtenga como entrada.
Gracias al sistema de tipos de Haskell, una acción nunca se puede usar en un contexto donde se espera otro tipo, y viceversa. Entonces, si desea encontrar la longitud de una cadena, puede usar la length
función:
length "Hello"
devolverá 5. Pero si desea encontrar la longitud de una cadena leída desde el terminal, no puede escribir
length (getLine)
porque obtiene un error de tipo: length
espera una entrada de la lista de tipos (y una Cadena es, de hecho, una lista) pero getLine
es un valor de tipo IO String
(una acción). De esta manera, el sistema de tipos asegura que un valor de acción como getLine
(cuya ejecución se lleva a cabo fuera del lenguaje central y que puede ser transparente no referencial) no puede ocultarse dentro de un valor de tipo de no acción Int
.
EDITAR
Para responder a una pregunta exizt, aquí hay un pequeño programa Haskell que lee una línea desde la consola e imprime su longitud.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
La acción principal consta de dos subacciones que se ejecutan secuencialmente:
getline
de tipo IO String
,
- el segundo se construye evaluando la función
putStrLn
de tipo String -> IO ()
en su argumento.
Más precisamente, la segunda acción es construida por
- vinculante
line
al valor leído por la primera acción,
- evaluar las funciones puras
length
(calcular la longitud como un entero) y luego show
(convertir el entero en una cadena),
- construyendo la acción aplicando la función
putStrLn
al resultado de show
.
En este punto, se puede ejecutar la segunda acción. Si ha escrito "Hola", imprimirá "5".
Tenga en cuenta que si obtiene un valor de una acción usando la <-
notación, solo puede usar ese valor dentro de otra acción, por ejemplo, no puede escribir:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
porque show (length line)
tiene tipo String
mientras que la notación do requiere que una acción ( getLine
de tipo IO String
) sea seguida por otra acción (por ejemplo, putStrLn (show (length line))
de tipo IO ()
).
EDITAR 2
La definición de Jörg W Mittag de transparencia referencial es más general que la mía (he votado su respuesta). He usado una definición restringida porque el ejemplo en la pregunta se enfoca en el valor de retorno de las funciones y quería ilustrar este aspecto. Sin embargo, RT en general se refiere al significado de todo el programa, incluidos los cambios en el estado global y las interacciones con el entorno (IO) causadas por la evaluación de una expresión. Entonces, para una definición correcta y general, debe referirse a esa respuesta.