22

私はしばらく関数型プログラミングの概念を扱っていますが、非常に興味深く、魅力的で、刺激的であると感じています。特に、純粋関数のアイデアは、さまざまな点で素晴らしいです。

しかし、私が得られないことが 1 つあります。それは、自分自身を純粋な関数に制限する場合の副作用に対処する方法です。

たとえば、2 つの数値の合計を計算したい場合は、(JavaScript で) 純粋な関数を記述できます。

var add = function (first, second) {
  return first + second;
};

全く問題無い。しかし、結果をコンソールに出力したい場合はどうすればよいでしょうか? 「何かをコンソールに出力する」タスクは定義上純粋ではありませんが、純粋な関数型プログラミング言語でこれをどのように処理できますか?

4

3 に答える 3

20

これにはいくつかのアプローチがあります。1 つだけ受け入れなければならないことは、ある時点で、純粋な表現を取り、環境と相互作用することによってそれらを不純にする魔法の不純な機械が存在するということです。この魔法の機械について質問してはいけません。

頭のてっぺんから考えることができる2つのアプローチがあります。私が忘れていた少なくとも 3 番目のものが存在します。


I/O ストリーム

最も理解しやすいアプローチは、ストリーミング I/O です。関数mainは 1 つの引数を取ります。それは、システムで発生したことのストリームです。これには、キーの押下、ファイル システム上のファイルなどが含まれます。このmain関数は、システム上で発生させたいことのストリームという 1 つのことも返します。

ストリームはリストのようなものです。一度に 1 つの要素のみを作成でき、作成するとすぐに受信者が要素を受け取ります。純粋なプログラムは、そのようなストリームから読み取り、システムに何かをさせたいときに独自のストリームに追加します。

このすべてを機能させる接着剤は、プログラムの外にあり、「要求」ストリームから読み取り、「回答」ストリームにデータを入れる魔法のマシンです。あなたのプログラムは純粋ですが、この魔法のマシンはそうではありません。

出力ストリームは次のようになります。

[print('Hello, world! What is your name?'), input(), create_file('G:\testfile'), create_file('C:\testfile'), write_file(filehandle, 'John')]

対応する入力ストリームは次のようになります

['John', IOException('There is no drive G:, could not create file!'), filehandle]

inputアウトストリームのインストリームがインストリームにどのように'John'表示されたかを確認してください。それが原則です。

モナディック I/O

モナディック I/O は Haskell が行っていることであり、非常にうまく機能します。これは、演算子を使用して I/O コマンドの巨大なツリーを構築し、それらを結合して、main関数がこの巨大な式をプログラムの外部にある魔法のマシンに返し、コマンドを実行し、指示された操作を実行することを想像できます。この魔法の機械は純粋ではありませんが、表現を構築するプログラムは純粋です。

このコマンド ツリーが次のようになっていると想像してみてください。

main
  |
  +---- Cmd_Print('Hello, world! What is your name?')
  +---- Cmd_WriteFile
           |
           +---- Cmd_Input
           |
           +---+ return validHandle(IOResult_attempt, IOResult_safe)
               + Cmd_StoreResult Cmd_CreateFile('G:\testfile') IOResult_attempt
               + Cmd_StoreResult Cmd_CreateFile('C:\testfile') IOResult_safe

最初に行うことは、挨拶を印刷することです。次に行うことは、ファイルを書きたいということです。ファイルに書き込むことができるようにするには、まず、ファイルに書き込むことになっているものを入力から読み取る必要があります。次に、書き込み先のファイルハンドルを持つことになっています。これはvalidHandle、2 つの選択肢の有効なハンドルを返す関数から取得されます。このようにして、純粋でないコードのように見えるものと純粋なコードのように見えるものを混在させることができます。


この「説明」は、質問してはいけない魔法の機械について質問することに近いので、いくつかの知恵でこれを締めくくります。

  • 実際のモナド I/O は、ここでの私の例とはかけ離れています。私の例は、モナド I/O が純粋さを損なうことなく「ボンネットの下」のように見える方法についての可能な説明の 1 つです。

  • 純粋な I/O の操作方法を理解するために私の例を使用しないでください。ボンネットの下で何かがどのように機能するかは、それをどのように使用するかとはまったく異なります。人生で一度も車を見たことがなければ、車の設計図を読んでも良いドライバーにはなれません。

    実際に物事を行う魔法の機械について質問するべきではないと私が言い続ける理由は、プログラマーが物事を学ぶとき、彼らはそれを理解しようとするために機械を突っ込みたがる傾向があるからです. 純粋な I/O の場合はお勧めしません。機械は、さまざまなバリアントの I/O の使用方法について何も教えてくれない場合があります。

    これは、逆アセンブルされた JVM バイトコードを見て Java を学ばないことに似ています。

  • モナドI/O とストリームベースの I/O の使い方を学びましょう。これは素晴らしい体験であり、ツールベルトの下にさらに多くのツールがあることは常に良いことです。

于 2013-08-11T16:02:50.927 に答える
6

純粋な関数型言語である Haskell は、「モナド」を使用して「不純な」関数を処理します。モナドとは基本的に、継続渡しによる関数呼び出しの連鎖を容易にするパターンです。概念的には、Haskell の print 関数は基本的に 3 つのパラメータを取ります: 出力する文字列、プログラムの状態、および残りのプログラムです。文字列が画面上にあるプログラムの新しい状態を渡しながら、残りのプログラムを呼び出します。このように、状態は変更されていません。

モナドがどのように機能するかについては、多くの詳細な説明があります。これは、何らかの理由で、モナドを理解するのが難しい概念だと人々が考えているためです。そうではありません。インターネットで検索するとたくさん見つかりますが、これは私が最も気に入っているものだと思います:

于 2013-08-11T14:51:17.270 に答える