Embora às vezes seja expresso dessa maneira, a programação funcional¹ não impede cálculos com estado. O que ele faz é forçar o programador a tornar o estado explícito.
Por exemplo, vamos pegar a estrutura básica de algum programa usando uma fila imperativa (em alguma pseudo-linguagem):
q := Queue.new();
while (true) {
if (Queue.is_empty(q)) {
Queue.add(q, producer());
} else {
consumer(Queue.take(q));
}
}
A estrutura correspondente com uma estrutura de dados da fila funcional (ainda em uma linguagem imperativa, para resolver uma diferença de cada vez) ficaria assim:
q := Queue.empty;
while (true) {
if (q = Queue.empty) {
q := Queue.add(q, producer());
} else {
(tail, element) := Queue.take(q);
consumer(element);
q := tail;
}
}
Como a fila agora é imutável, o próprio objeto não muda. Nesse pseudo-código, q
ele próprio é uma variável; as atribuições q := Queue.add(…)
e q := tail
faça com que aponte para um objeto diferente. A interface das funções da fila mudou: cada uma deve retornar o novo objeto da fila resultante da operação.
Em uma linguagem puramente funcional, ou seja, em uma linguagem sem efeito colateral, você precisa explicitar todos os estados. Como o produtor e o consumidor provavelmente estão fazendo algo, o estado deles também deve estar na interface do chamador.
main_loop(q, other_state) {
if (q = Queue.empty) {
let (new_state, element) = producer(other_state);
main_loop(Queue.add(q, element), new_state);
} else {
let (tail, element) = Queue.take(q);
let new_state = consumer(other_state, element);
main_loop(tail, new_state);
}
}
main_loop(Queue.empty, initial_state)
Observe como agora cada parte do estado é gerenciada explicitamente. As funções de manipulação de fila tomam uma fila como entrada e produzem uma nova fila como saída. O produtor e o consumidor também passam seu estado.
A programação simultânea não se encaixa tão bem na programação funcional, mas se encaixa muito bem na programação funcional. A idéia é executar vários nós de computação separados e permitir que eles troquem mensagens. Cada nó executa um programa funcional e seu estado muda à medida que envia e recebe mensagens.
Continuando o exemplo, como há uma única fila, ela é gerenciada por um nó específico. Os consumidores enviam uma mensagem a esse nó para obter um elemento. Os produtores enviam a esse nó uma mensagem para adicionar um elemento.
main_loop(q) =
consumer->consume(q->take()) || q->add(producer->produce());
main_loop(q)
A única linguagem "industrializada" que acerta a concorrência³ é Erlang . Aprender Erlang é definitivamente o caminho para a iluminação⁴ sobre programação simultânea.
Todo mundo muda para idiomas sem efeitos colaterais agora!
Term Este termo tem vários significados; aqui eu acho que você está usando isso para significar programação sem efeitos colaterais, e esse é o significado que eu também estou usando.
² Programar com estado implícito é uma programação imperativa ; a orientação a objetos é uma preocupação completamente ortogonal.
³ Inflamatório, eu sei, mas estou falando sério. Threads com memória compartilhada é a linguagem assembly da programação simultânea. A passagem de mensagens é muito mais fácil de entender, e a falta de efeitos colaterais realmente brilha assim que você introduz a simultaneidade.
⁴ E isso vem de alguém que não é fã de Erlang, mas por outros motivos.