Tecnicamente, qualquer programa que você executa em um computador é impuro porque, eventualmente, compila instruções como "mover esse valor para eax
" e "adicionar esse valor ao conteúdo de eax
", que são impuros. Isso não é muito útil.
Em vez disso, pensamos em pureza usando caixas pretas . Se algum código sempre produz as mesmas saídas quando recebe as mesmas entradas, é considerado puro. Por essa definição, a função a seguir também é pura, embora internamente use uma tabela de notas impuras.
const fib = (() => {
const memo = [0, 1];
return n => {
if (n >= memo.length) memo[n] = fib(n - 1) + fib(n - 2);
return memo[n];
};
})();
console.log(fib(100));
Não nos importamos com os internos porque estamos usando uma metodologia de caixa preta para verificar a pureza. Da mesma forma, não nos importamos que todo o código seja eventualmente convertido em instruções impuras da máquina, porque estamos pensando em pureza usando uma metodologia de caixa preta. Internos não são importantes.
Agora, considere a seguinte função.
const greet = name => {
console.log("Hello %s!", name);
};
greet("World");
greet("Snowman");
A greet
função é pura ou impura? Pela nossa metodologia de caixa preta, se fornecermos a mesma entrada (por exemplo,World
), ela sempre imprimirá a mesma saída na tela (por exemplo Hello World!
). Nesse sentido, não é puro? Não, não é. A razão pela qual não é pura é porque consideramos a impressão de algo na tela um efeito colateral. Se nossa caixa preta produz efeitos colaterais, ela não é pura.
O que é um efeito colateral? É aqui que o conceito de transparência referencial é útil. Se uma função é referencialmente transparente, sempre podemos substituir aplicativos dessa função pelos seus resultados. Observe que isso não é o mesmo que função embutida .
Na função inlining, substituímos aplicativos de uma função pelo corpo da função sem alterar a semântica do programa. No entanto, uma função referencialmente transparente pode sempre ser substituída pelo seu valor de retorno sem alterar a semântica do programa. Considere o seguinte exemplo.
console.log("Hello %s!", "World");
console.log("Hello %s!", "Snowman");
Aqui, destacamos a definição de greet
e não mudou a semântica do programa.
Agora, considere o seguinte programa.
Aqui, substituímos as aplicações do greet
função pelos seus valores de retorno e isso mudou a semântica do programa. Não estamos mais imprimindo saudações na tela. Essa é a razão pela qual a impressão é considerada um efeito colateral, e é por isso que a greet
função é impura. Não é referencialmente transparente.
Agora, vamos considerar outro exemplo. Considere o seguinte programa.
const main = async () => {
const response = await fetch("https://time.akamai.com/");
const serverTime = 1000 * await response.json();
const timeDiff = time => time - serverTime;
console.log("%d ms", timeDiff(Date.now()));
};
main();
Claramente, a main
função é impura. No entanto, é otimeDiff
função é pura ou impura? Embora dependa deserverTime
origem de uma chamada de rede impura, ela ainda é referencialmente transparente porque retorna as mesmas saídas para as mesmas entradas e porque não tem efeitos colaterais.
Os zerkms provavelmente discordarão de mim neste ponto. Em sua resposta , ele disse que a dollarToEuro
função no exemplo a seguir é impura porque "depende do IO transitivamente".
const exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x, exchangeRate) => {
return x * exchangeRate;
};
Eu tenho que discordar dele porque o fato de o exchangeRate
veio de um banco de dados é irrelevante. É um detalhe interno e nossa metodologia de caixa preta para determinar a pureza de uma função não se importa com detalhes internos.
Em linguagens puramente funcionais como Haskell, temos uma saída para executar efeitos arbitrários de E / S. É chamadounsafePerformIO
e, como o nome indica, se você não o usar corretamente, não será seguro, pois pode quebrar a transparência referencial. No entanto, se você sabe o que está fazendo, é perfeitamente seguro usá-lo.
Geralmente é usado para carregar dados de arquivos de configuração perto do início do programa. Carregar dados de arquivos de configuração é uma operação de E / S impura. No entanto, não queremos ser sobrecarregados ao passar os dados como entradas para todas as funções. Portanto, se usarmosunsafePerformIO
, podemos carregar os dados no nível superior e todas as nossas funções puras podem depender dos imutáveis dados globais de configuração.
Observe que apenas porque uma função depende de alguns dados carregados de um arquivo de configuração, um banco de dados ou uma chamada de rede, não significa que a função seja impura.
No entanto, vamos considerar o seu exemplo original, que tem semântica diferente.
let exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x) => {
return x * exchangeRate;
};
dollarToEuro(100) //90 today
dollarToEuro(100) //something else tomorrow
Aqui, estou assumindo que, porque exchangeRate
não está definido como const
, será modificado enquanto o programa estiver em execução. Se for esse o caso, entãodollarToEuro
é definitivamente uma função impura, porque quando oexchangeRate
é modificada, ela quebra a transparência referencial.
No entanto, se a exchangeRate
variável não for modificada e nunca será modificada no futuro (ou seja, se for um valor constante), mesmo que seja definida como let
, ela não quebrará a transparência referencial. Nesse caso,dollarToEuro
é de fato uma função pura.
Observe que o valor de exchangeRate
pode mudar sempre que você executar o programa novamente e não quebrará a transparência referencial. Ele só quebra a transparência referencial se for alterado enquanto o programa estiver em execução.
Por exemplo, se você executar meu timeDiff
exemplo várias vezes, obterá valores diferentes para serverTime
e, portanto, resultados diferentes. No entanto, como o valor de serverTime
nunca muda enquanto o programa está sendo executado, a timeDiff
função é pura.
function myNumber(n) { this.n = n; }; myNumber.prototype.valueOf = function() { console.log('impure'); return this.n; }; const n = new myNumber(42); add(n, 1);