A chave da programação funcional não é que não exista um estado, é que exista um estado explícito .
O que isso significa é que seu estado é passado como parâmetro para suas funções. É um valor real, que você pode obter, examinar e passar para outras funções.
Por exemplo, vejamos o método de programação dinâmica para calcular os números de Fibonacci. Em uma linguagem imperativa, você teria algo parecido com isto:
def fib(n):
A = {}
A[0] = 0
A[1] = 1
for i in [2 .. n+1]:
A[i] = A[i-1] + A[i-2]
return A[n]
Para fazer isso sem estado, basta passar explicitamente sua loja. Usando a sintaxe Haskell-ish:
fib n = fibHelper 2 n {(1,1), (0,0)}
fibHelper i end cache =
if
i > end
then
lookup end cache
else
let
newVal = (lookup (i-1) cache) + (lookup (i-2) cache)
newCache = insert i newVal cache
in
fibHelper (i+1) end newCache
Agora, isso é um pouco artificial, já que você não precisa de toda a matriz para os números de Fibonacci, mas pode imaginar usá-la para problemas de programação dinâmica mais complicados, como o Knapsack, onde você precisa de todo o conjunto de valores calculados anteriormente.
O principal a entender aqui é que insert
é uma função que pega uma loja e retorna uma nova loja, que é igual ao original com um novo valor agregado. O valor original de cache
não é destruído; portanto, se você tiver um aplicativo em que precise de algum tipo de operação "desfazer", poderá acompanhar o histórico do seu estado.
Você pode dizer "mas isso parece ineficiente! Você está criando uma loja totalmente nova a cada vez!" No entanto, geralmente em linguagens funcionais, essas coisas são implementadas de maneira inteligente usando referências, para que não haja cópias totalmente novas dos dados.
Também vale a pena mencionar que esse padrão de ter um parâmetro de estado, transmitindo-o à medida que o cálculo avança e modificando-o, é muito comum. As pessoas inventaram abstrações, como a mônada do Estado , que permite escrever coisas que parecem imperativas, mas que são puramente funcionais "sob o capô".