A transparência referencial, referida a uma função, indica que você pode determinar o resultado da aplicação dessa função apenas observando os valores de seus argumentos. Você pode escrever funções transparentemente referenciais em qualquer linguagem de programação, por exemplo, Python, Scheme, Pascal, C.
Por outro lado, na maioria dos idiomas, você também pode escrever funções transparentes e não referenciais. Por exemplo, esta função Python:
counter = 0
def foo(x):
global counter
counter += 1
return x + counter
não é referencialmente transparente, chamando de fato
foo(x) + foo(x)
e
2 * foo(x)
produzirá valores diferentes, para qualquer argumento x
. A razão para isso é que a função usa e modifica uma variável global; portanto, o resultado de cada chamada depende desse estado de alteração e não apenas do argumento da função.
Haskell, uma linguagem puramente funcional, separa estritamente a avaliação da expressão na qual funções puras são aplicadas e sempre referencialmente transparentes, da execução da ação (processamento de valores especiais), que não é referencialmente transparente, ou seja, executar a mesma ação sempre que um resultado diferente.
Portanto, para qualquer função Haskell
f :: Int -> Int
e qualquer número inteiro x
, é sempre verdade que
2 * (f x) == (f x) + (f x)
Um exemplo de uma ação é o resultado da função de biblioteca getLine
:
getLine :: IO String
Como resultado da avaliação da expressão, essa função (na verdade uma constante) produz primeiro um valor puro do tipo IO String
. Valores desse tipo são valores como qualquer outro: você pode distribuí-los, colocá-los em estruturas de dados, compor usando funções especiais e assim por diante. Por exemplo, você pode fazer uma lista de ações da seguinte forma:
[getLine, getLine] :: [IO String]
As ações são especiais, pois você pode dizer ao tempo de execução do Haskell para executá-las escrevendo:
main = <some action>
Nesse caso, quando o programa Haskell é iniciado, o tempo de execução percorre a ação vinculada main
e a executa , possivelmente produzindo efeitos colaterais. Portanto, a execução da ação não é referencialmente transparente porque a execução da mesma ação duas vezes pode produzir resultados diferentes, dependendo do que o tempo de execução obtém como entrada.
Graças ao sistema de tipos de Haskell, uma ação nunca pode ser usada em um contexto em que outro tipo é esperado e vice-versa. Portanto, se você deseja encontrar o comprimento de uma string, pode usar a length
função:
length "Hello"
retornará 5. Mas se você quiser encontrar o comprimento de uma string lida no terminal, não poderá escrever
length (getLine)
porque você recebe um erro de tipo: length
espera uma entrada do tipo lista (e uma String é, de fato, uma lista), mas getLine
é um valor do tipo IO String
(uma ação). Dessa maneira, o sistema de tipos garante que um valor de ação como getLine
(cuja execução é executada fora da linguagem principal e que pode ser transparente não referencialmente) não possa ser oculto dentro de um valor de tipo não-ação Int
.
EDITAR
Para responder à pergunta anterior, aqui está um pequeno programa Haskell que lê uma linha do console e imprime seu comprimento.
main :: IO () -- The main program is an action of type IO ()
main = do
line <- getLine
putStrLn (show (length line))
A ação principal consiste em duas subações que são executadas sequencialmente:
getline
de tipo IO String
,
- o segundo é construído avaliando a função
putStrLn
do tipo String -> IO ()
em seu argumento.
Mais precisamente, a segunda ação é construída por
- vinculação
line
ao valor lido pela primeira ação,
- avaliar as funções puras
length
(calcular o comprimento como um número inteiro) e depois show
(transformar o número inteiro em uma sequência de caracteres),
- construindo a ação aplicando a função
putStrLn
ao resultado de show
.
Nesse ponto, a segunda ação pode ser executada. Se você digitou "Olá", ele imprimirá "5".
Observe que, se você obtiver um valor de uma ação usando a <-
notação, você só poderá usar esse valor dentro de outra ação, por exemplo, você não poderá escrever:
main = do
line <- getLine
show (length line) -- Error:
-- Expected type: IO ()
-- Actual type: String
porque show (length line)
tem type String
enquanto a notação requer que uma ação ( getLine
do tipo IO String
) seja seguida por outra ação (por exemplo, putStrLn (show (length line))
do tipo IO ()
).
EDIT 2
A definição de transparência referencial de Jörg W. Mittag é mais geral que a minha (eu votei positivamente sua resposta). Eu usei uma definição restrita porque o exemplo da pergunta se concentra no valor de retorno das funções e queria ilustrar esse aspecto. No entanto, RT geralmente se refere ao significado de todo o programa, incluindo alterações no estado global e interações com o ambiente (IO) causadas pela avaliação de uma expressão. Portanto, para uma definição geral correta, você deve consultar essa resposta.