関数型プログラミング¹は、そのように表現されることもありますが、ステートフルな計算を妨げません。それはプログラマに状態を明示的にさせることです。
たとえば、命令型キューを使用して(一部の疑似言語で)プログラムの基本構造を見てみましょう。
q := Queue.new();
while (true) {
if (Queue.is_empty(q)) {
Queue.add(q, producer());
} else {
consumer(Queue.take(q));
}
}
機能キューデータ構造を持つ対応する構造(一度に1つの違いに対処するために、命令型言語で)は次のようになります。
q := Queue.empty;
while (true) {
if (q = Queue.empty) {
q := Queue.add(q, producer());
} else {
(tail, element) := Queue.take(q);
consumer(element);
q := tail;
}
}
キューは不変であるため、オブジェクト自体は変更されません。この擬似コードでq
は、それ自体が変数です。割り当てq := Queue.add(…)
をq := tail
行い、別のオブジェクトを指すようにします。キュー関数のインターフェイスが変更されました。それぞれが、操作の結果である新しいキューオブジェクトを返す必要があります。
純粋に機能的な言語、つまり副作用のない言語では、すべての状態を明示的にする必要があります。プロデューサーとコンシューマーはおそらく何かをしているので、それらの状態もここの呼び出し元のインターフェースになければなりません。
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)
どのようにしてすべての状態が明示的に管理されるかに注意してください。キュー操作関数は、キューを入力として受け取り、出力として新しいキューを生成します。プロデューサーとコンシューマーも同様に状態を渡します。
並行プログラミングはとてもうまく合わないの内側に関数型プログラミング、それは非常によく適合周りの関数型プログラミング。アイデアは、多数の個別の計算ノードを実行し、それらにメッセージを交換させることです。各ノードは機能プログラムを実行し、メッセージの送受信に応じて状態が変化します。
例を続けると、キューが1つしかないため、1つの特定のノードによって管理されます。消費者はそのノードにメッセージを送信して要素を取得します。プロデューサーはそのノードにメッセージを送信して要素を追加します。
main_loop(q) =
consumer->consume(q->take()) || q->add(producer->produce());
main_loop(q)
並行処理を正しく行う「工業化された」言語はErlangです。Erlangを学ぶことは、間違いなく並行プログラミングについての啓発への道です。
誰もが副作用のない言語に今すぐ切り替えます!
¹ この用語にはいくつかの意味があります。ここで私はあなたが副作用なしでプログラミングを意味するためにそれを使用していると思う、そしてそれは私も使用している意味だ。
² 暗黙的な状態でのプログラミングは、命令型プログラミングです。オブジェクトの向きは完全に直交する問題です。
³ 炎症性、私は知っていますが、私はそれを意味します。共有メモリを持つスレッドは、並行プログラミングのアセンブリ言語です。メッセージの受け渡しは非常に理解しやすく、同時実行性を導入するとすぐに副作用の欠如が明らかになります。
⁴ これは、Erlangのファンではないが、他の理由からだ。