Ou, se você joga um videogame, existem inúmeras variáveis de estado, começando pelas posições de todos os personagens, que tendem a se mover constantemente. Como você pode fazer algo útil sem acompanhar as alterações nos valores?
Se você estiver interessado, aqui está uma série de artigos que descrevem a programação de jogos com Erlang.
Você provavelmente não gostará desta resposta, mas não receberá um programa funcional até usá-la. Eu posso postar exemplos de código e dizer "Aqui, você não vê " - mas se você não entende a sintaxe e os princípios subjacentes, seus olhos simplesmente brilham. Do seu ponto de vista, parece que estou fazendo a mesma coisa que uma linguagem imperativa, mas apenas configurando todos os tipos de limites para propositalmente dificultar a programação. Meu ponto de vista, você está apenas experimentando o paradoxo de Blub .
Eu fiquei cético no começo, mas entrei no trem de programação funcional há alguns anos e me apaixonei por ele. O truque da programação funcional é poder reconhecer padrões, atribuições de variáveis específicas e mover o estado imperativo para a pilha. Um loop for, por exemplo, torna-se recursão:
// Imperative
let printTo x =
for a in 1 .. x do
printfn "%i" a
// Recursive
let printTo x =
let rec loop a = if a <= x then printfn "%i" a; loop (a + 1)
loop 1
Não é muito bonito, mas temos o mesmo efeito sem mutação. Obviamente, sempre que possível, gostamos de evitar repetições e simplesmente abstraí-lo:
// Preferred
let printTo x = seq { 1 .. x } |> Seq.iter (fun a -> printfn "%i" a)
O método Seq.iter enumerará através da coleção e chamará a função anônima para cada item. Muito conveniente :)
Eu sei, imprimir números não é exatamente impressionante. No entanto, podemos usar a mesma abordagem nos jogos: mantenha todo o estado na pilha e crie um novo objeto com nossas alterações na chamada recursiva. Dessa forma, cada quadro é um instantâneo sem estado do jogo, onde cada quadro simplesmente cria um novo objeto com as alterações desejadas de qualquer objeto sem estado que precise ser atualizado. O pseudocódigo para isso pode ser:
// imperative version
pacman = new pacman(0, 0)
while true
if key = UP then pacman.y++
elif key = DOWN then pacman.y--
elif key = LEFT then pacman.x--
elif key = UP then pacman.x++
render(pacman)
// functional version
let rec loop pacman =
render(pacman)
let x, y = switch(key)
case LEFT: pacman.x - 1, pacman.y
case RIGHT: pacman.x + 1, pacman.y
case UP: pacman.x, pacman.y - 1
case DOWN: pacman.x, pacman.y + 1
loop(new pacman(x, y))
As versões imperativa e funcional são idênticas, mas a versão funcional claramente não usa estado mutável. O código funcional mantém todo o estado mantido na pilha - o bom dessa abordagem é que, se algo der errado, a depuração é fácil, tudo o que você precisa é de um rastreamento de pilha.
Isso aumenta para qualquer número de objetos no jogo, porque todos os objetos (ou coleções de objetos relacionados) podem ser renderizados em seu próprio encadeamento.
Quase todos os aplicativos de usuário em que consigo pensar envolvem estado como um conceito central.
Nas linguagens funcionais, em vez de alterar o estado dos objetos, simplesmente retornamos um novo objeto com as alterações que desejamos. É mais eficiente do que parece. Estruturas de dados, por exemplo, são muito fáceis de representar como estruturas de dados imutáveis. Pilhas, por exemplo, são notoriamente fáceis de implementar:
using System;
namespace ConsoleApplication1
{
static class Stack
{
public static Stack<T> Cons<T>(T hd, Stack<T> tl) { return new Stack<T>(hd, tl); }
public static Stack<T> Append<T>(Stack<T> x, Stack<T> y)
{
return x == null ? y : Cons(x.Head, Append(x.Tail, y));
}
public static void Iter<T>(Stack<T> x, Action<T> f) { if (x != null) { f(x.Head); Iter(x.Tail, f); } }
}
class Stack<T>
{
public readonly T Head;
public readonly Stack<T> Tail;
public Stack(T hd, Stack<T> tl)
{
this.Head = hd;
this.Tail = tl;
}
}
class Program
{
static void Main(string[] args)
{
Stack<int> x = Stack.Cons(1, Stack.Cons(2, Stack.Cons(3, Stack.Cons(4, null))));
Stack<int> y = Stack.Cons(5, Stack.Cons(6, Stack.Cons(7, Stack.Cons(8, null))));
Stack<int> z = Stack.Append(x, y);
Stack.Iter(z, a => Console.WriteLine(a));
Console.ReadKey(true);
}
}
}
O código acima constrói duas listas imutáveis, as anexa para criar uma nova lista e os resultados. Nenhum estado mutável é usado em qualquer lugar do aplicativo. Parece um pouco volumoso, mas isso é apenas porque o C # é uma linguagem detalhada. Aqui está o programa equivalente em F #:
type 'a stack =
| Cons of 'a * 'a stack
| Nil
let rec append x y =
match x with
| Cons(hd, tl) -> Cons(hd, append tl y)
| Nil -> y
let rec iter f = function
| Cons(hd, tl) -> f(hd); iter f tl
| Nil -> ()
let x = Cons(1, Cons(2, Cons(3, Cons(4, Nil))))
let y = Cons(5, Cons(6, Cons(7, Cons(8, Nil))))
let z = append x y
iter (fun a -> printfn "%i" a) z
Não é necessário mutável para criar e manipular listas. Quase todas as estruturas de dados podem ser facilmente convertidas em seus equivalentes funcionais. Eu escrevi uma página aqui que fornece implementações imutáveis de pilhas, filas, montes de esquerda, árvores vermelho-pretas, listas preguiçosas. Nenhum trecho de código contém um estado mutável. Para "modificar" uma árvore, crio uma nova com o novo nó que desejo - isso é muito eficiente porque não preciso fazer uma cópia de todos os nós da árvore, posso reutilizar os antigos no meu novo árvore.
Usando um exemplo mais significativo, também escrevi esse analisador SQL totalmente sem estado (ou pelo menos meu código é sem estado, não sei se a biblioteca lexing subjacente é sem estado).
A programação sem estado é tão expressiva e poderosa quanto a programação com estado, requer apenas um pouco de prática para se treinar para começar a pensar sem estado. Obviamente, "programação sem estado, quando possível, programação com estado sempre que necessário" parece ser o lema da maioria das linguagens funcionais impuras. Não há mal nenhum em recorrer a mutáveis quando a abordagem funcional simplesmente não é tão limpa ou eficiente.