Tentarei ilustrar a abordagem de Haskell (não tenho certeza de que minha intuição esteja 100% correta, pois não sou especialista em Haskell, as correções são bem-vindas).
Seu código pode ser escrito em Haskell da seguinte maneira:
import System.CPUTime
f :: Integer -> Integer -> IO Integer
f a b = do
t <- getCPUTime
return (a + b + (div t 1000000000000))
Então, onde está a transparência referencial?
f
é uma função que, dados dois inteiros a
e b
, criará uma ação, como você pode ver pelo tipo de retorno IO Integer
. Essa ação sempre será a mesma, considerando os dois números inteiros; portanto, a função que mapeia um par de números inteiros para ações de IO é referencialmente transparente.
Quando essa ação é executada, o valor inteiro produzido depende do tempo atual da CPU: executar ações NÃO é uma aplicação de função.
Resumindo: No Haskell, você pode usar funções puras para construir e combinar ações complexas (seqüenciamento, composição de ações etc.) de maneira referencialmente transparente. Novamente, observe que no exemplo acima a função pura f
não retorna um número inteiro: ela retorna uma ação.
EDITAR
Mais alguns detalhes sobre a pergunta JohnDoDo.
O que significa que "executar ações NÃO é uma aplicação funcional"?
Dados os conjuntos T1, T2, Tn, T, uma função f é um mapeamento (relação) que se associa a cada tupla em T1 x T2 x ... x Tn um valor em T. Portanto, a aplicação da função produz um valor de saída, considerando alguns valores de entrada . Usando esse mecanismo, você pode construir expressões que avaliam valores, por exemplo, o valor 10
é o resultado da avaliação da expressão 4 + 6
. Observe que, ao mapear valores para valores dessa maneira, você não está executando nenhum tipo de entrada / saída.
Em Haskell, ações são valores de tipos especiais que podem ser construídos avaliando expressões contendo funções puras apropriadas que funcionam com ações. Dessa maneira, um programa Haskell é uma ação composta obtida pela avaliação da main
função. Esta ação principal tem tipo IO ()
.
Depois que essa ação composta é definida, outro mecanismo (não o aplicativo de funções) é usado para invocar / executar a ação (veja, por exemplo, aqui ). Toda a execução do programa é o resultado da invocação da ação principal, que por sua vez pode invocar sub-ações. Esse mecanismo de chamada (cujos detalhes internos eu não conheço) cuida da execução de todas as chamadas de E / S necessárias, possivelmente acessando o terminal, o disco, a rede e assim por diante.
Voltando ao exemplo. A função f
acima não retorna um número inteiro e você não pode escrever uma função que execute E / S e retorne um número inteiro ao mesmo tempo: você deve escolher um dos dois.
O que você pode fazer é incorporar a ação retornada por f 2 3
em uma ação mais complexa. Por exemplo, se você deseja imprimir o número inteiro produzido por essa ação, você pode escrever:
main :: IO ()
main = do
x <- f 2 3
putStrLn (show x)
A do
notação indica que a ação retornada pela função principal é obtida por uma composição seqüencial de duas ações menores e a x <-
notação indica que o valor produzido na primeira ação deve ser passado para a segunda ação.
Na segunda ação
putStrLn (show x)
o nome x
é vinculado ao número inteiro produzido executando a ação
f 2 3
Um ponto importante é que o número inteiro produzido quando a primeira ação é chamada pode viver apenas dentro das ações de E / S: pode ser passado de uma ação de E / S para a seguinte, mas não pode ser extraído como um valor inteiro simples.
Compare a main
função acima com esta:
main = do
let y = 2 + 3
putStrLn (show y)
Nesse caso, existe apenas uma ação, a saber putStrLn (show y)
, e y
está vinculada ao resultado da aplicação da função pura +
. Também podemos definir esta ação principal da seguinte maneira:
main = putStrLn "5"
Então, observe a sintaxe diferente
x <- f 2 3 -- Inject the value produced by an action into
-- the following IO actions.
-- The value may depend on when the action is
-- actually executed. What happens when the action is
-- executed is not known here: it may get user input,
-- access the disk, the network, the system clock, etc.
let y = 2 + 3 -- Bind y to the result of applying the pure function `+`
-- to the arguments 2 and 3.
-- The value depends only on the arguments 2 and 3.
Sumário
- No Haskell, funções puras são usadas para construir as ações que constituem um programa.
- Ações são valores de um tipo especial.
- Como as ações são construídas aplicando funções puras, a construção de ações é referencialmente transparente.
- Após a construção de uma ação, ela pode ser chamada usando um mecanismo separado.