純粋および不純な関数型プログラミング
純粋関数は本質的に参照透過性であり、メモ化(結果のキャッシュ)が可能です。可変状態がないため、再入可能であり、リンクされたデータ構造のさまざまなバージョンでメモリを共有でき、自動並列化が可能になります。重要なのは、状態の変化を制限することで、命令型プログラミングの多くの複雑な問題について考える必要がなくなるということです。
ただし、この制限には欠点があります。1つはパフォーマンスです。一部のアルゴリズムとデータ構造(ハッシュテーブルの作成など)は、大量のデータをコピーせずに純粋関数として表現することはできません。もう1つ:純粋な関数型言語であるHaskellと比較してください。突然変異は(概念的に)存在しないため、モナドを使用して状態の変化を表す必要があります。(Haskellはかなり簡潔なdo
表記の構文糖衣を提供しますが、状態モナド内のプログラミングは「通常の」Haskellとはまったく異なる言語です!)状態を変更するいくつかのインターロッキングループを使用してアルゴリズムを最も簡単に表現できる場合、Haskellの実装はより不格好になります不純な言語で可能なことよりも。
例として、XMLドキュメント内に深くネストされた単一のノードを変更します。ジッパーデータ構造を使用することは可能ですが、状態の変化なしではより困難です。擬似コードの例(純粋):
old_xml = <a><b><c><d><e><f><g><h attrib="oldvalue"/></g></f></e></d></c></b></a>
// '\' is the XML selection operator
node_to_change = orig_xml \ "a" \ "b" \ "c" \ "d" \ "e" \ "f" \ "g" \ "h"
node_changed = node_to_change.copy("attrib" -> "newvalue")
new_xml = node_changed.unselect().unselect().unselect().unselect()
.unselect().unselect().unselect().unselect()
return new_xml
例(不純):
xml = <a><b><c><d><e><f><g><h attrib="oldvalue"/></g></f></e></d></c></b></a>
node_to_change = orig_xml.select_by_xpath("/a/b/c/d/e/f/g/h")
node_to_change.set("attrib" -> "newvalue")
return xml // xml has already been updated
純粋関数型データ構造の詳細については、https://cstheory.stackexchange.com/questions/1539/whats-new-in-purely-functional-data-structures-since-okasakiを参照してください。(さらに、内部状態のみを操作する手続き型関数を記述して、呼び出し元にとって効果的に純粋関数になるようにラップすることができる場合がよくあります。これは、不純な言語では、そうでないため、少し簡単です。状態モナドで記述し、に渡す必要がありrunST
ます。)
不純なスタイルで書くと、これらの利点は失われますが、関数型プログラミングの他の便利な機能(高階関数など)のいくつかは引き続き適用されます。
突然変異を使用する
Lispは不純な関数型言語であり、状態の変化を可能にすることを意味します。これは仕様によるものであるため、ミューテーションが必要な場合は、実際には別の言語を使用しなくても使用できます。
一般的に、はい、次の場合に状態ミューテーションを使用することは問題ありません
- パフォーマンス上の理由で必要です。
- ミューテーションを使用すると、アルゴリズムをより明確に表現できます。
あなたがそうするとき:
uniquify
関数が渡すリストを変更することを明確に文書化します。呼び出し元が関数に変数を渡して、それを変更して戻すのは厄介です。
- アプリケーションがマルチスレッドの場合は、不純な関数がスレッドセーフであるかどうかを分析し、認識し、文書化します。