web-dev-qa-db-ja.com

IOモナドパターンの利点は、副作用を純粋に学術的に扱うためのものですか?

さらに申し訳ありませんFP +副作用の質問ですが、これに対して完全に答えた既存の質問は見つかりませんでした。

関数型プログラミングについての私の(限られた)理解は、状態/副作用を最小限に抑え、ステートレスロジックから分離する必要があることです。

IOモナド、これに対するHaskellのアプローチも収集します。これは、後で実行するために、プログラム自体の範囲外と見なされるコンテナにステートフルアクションをラップすることで実現します。

私はこのパターンを理解しようとしていますが、実際にPythonプロジェクトで使用するかどうかを決定するため、可能な場合はHaskellの詳細を避けたいと思います。

原油入荷予定。

私のプログラムがXMLファイルをJSONファイルに変換する場合:

_def main():
    xml_data = read_file('input.xml')  # impure
    json_data = convert(xml_data)  # pure
    write_file('output.json', json_data) # impure
_

IOモナドの効果的な方法ではありません:

_steps = list(
    read_file,
    convert,
    write_file,
)
_

次に、実際にはcallingではなく、インタプリタに任せて責任を免除しますか?

または別の言い方をすると、それは書くようなものです。

_def main():  # pure
    def inner():  # impure
        xml_data = read_file('input.xml')
        json_data = convert(xml_data)
        write_file('output.json', json_data)
    return inner
_

次に、誰かがinner()を呼び出すことを期待し、main()が純粋であるため、ジョブが完了したと言います。

プログラム全体は、基本的にIOモナドに含まれることになります。

コードが実際にexecutedである場合、ファイルを読み取った後のすべてはそのファイルの状態に依存するため、命令型実装と同じ状態関連のバグが発生するため、プログラマーとして実際に何かを得ましたか誰がこれを維持しますか?

reduceisolatingのステートフルな動作の利点に完全に感謝します。これが実際に、私がそのような命令バージョンを構成した理由です。入力を収集し、純粋なものを実行し、出力を吐き出します。うまくいけば、convert()は完全に純粋になり、キャッシュ可能性、スレッドセーフなどのメリットを享受できます。

特に比較可能な型で動作するパイプラインでモナディック型が役立つこともありがたく思いますが、IOがそのようなパイプライン内にない限り、なぜモナドを使用する必要があるのか​​わかりません。

IOモナドパターンがもたらす副作用に対処することには、他に利点がありますか?

17
Stu Cox

プログラム全体は、基本的にIOモナドに含まれることになります。

それは、Haskellersの観点から見たものではないと私が思うところです。したがって、次のようなプログラムがあります。

module Main

main :: IO ()
main = do
  xmlData <- readFile "input.xml"
  let jsonData = convert xmlData
  writeFile "output.json" jsonData

convert :: String -> String
convert xml = ...

これに対する典型的なHaskellerの見解は、純粋な部分であるconvertだと思います。

  1. おそらくこのプログラムの大部分であり、IOの部分よりもはるかに複雑です。
  2. IOをまったく扱わずに、推論とテストを行うことができます。

そのため、彼らはこれをconvertIOに「含まれる」とは見なさず、むしろisolatedからIOであると見なします。そのタイプから、convertが行うことは、IOアクションで発生することに依存することはできません。

コードが実際に実行されると、ファイルを読み取った後のすべてがそのファイルの状態に依存するため、命令型実装と同じ状態関連のバグが発生するため、これを維持するプログラマーとして実際に何かを得ましたか?

これは次の2つに分かれると思います。

  1. プログラムの実行時、convertへの引数の値は、ファイルの状態によって異なります。
  2. しかし、convert関数が行うことは、ファイルの状態に依存しません。 convertは常に同じ関数であり、異なる場所で異なる引数で呼び出された場合でも同様です。

これはやや抽象的な点ですが、Haskellersがこれについて話すときに何を意味するのかは非常に重要です。 any有効な引数を指定してconvertを記述したい場合、その引数に対して正しい結果が生成されます。このように見ると、ファイルの読み取りがステートフルな操作であるという事実は方程式には入りません。重要なのは、引数が与えられても、それがどこから来ても、convertはそれを正しく処理する必要があるということです。そして、純粋さがconvertがその入力で実行できることを制限するという事実は、その推論を単純化します。

したがって、convertがいくつかの引数から誤った結果を生成し、readFileがそのような引数をフィードする場合、state。純粋な関数のバグです。

14
sacundim

「純粋に学術的」とは、あなたが何を意味するのか正確に理解するのは難しいですが、答えはほとんど「ノー」だと思います。

厄介な分隊への取り組み によるSimon Peyton Jones(stronglyの推奨読書!)で説明されているように、モナドI/OはHaskellがI/Oを処理するために使用した方法で実際の問題を解決します。ここではコピーしませんが、リクエストとレスポンスを備えたサーバーの例を読んでください。それは非常に有益です。

Pythonとは異なり、Haskellは、その型システムによって強制できる「純粋な」計算のスタイルを推奨しています。もちろん、このスタイルに準拠するためにPythonでプログラミングするときに自己規律を使用できますが、自分で作成しなかったモジュールについてはどうですか?タイプシステム(および一般的なライブラリ)からの多くの助けなしに、モナディックI/OはおそらくPythonではあまり有用ではありません。言語の哲学は、厳密な純粋/不純な分離を強制することを意図したものではありません。

これは、HaskellとPythonのさまざまな哲学について、アカデミックモナディックI/Oについてではなく、Pythonでは使用しないことを示しています。

もう1つ。あなたは言う:

プログラム全体は、基本的にIOモナドに含まれることになります。

確かにHaskellのmain関数はIOに「存在」しますが、実際のHaskellプログラムでは、不要な場合はIOを使用しないことをお勧めします。 I/Oを行う必要のないほぼすべての関数は、タイプIOであってはなりません。

だから私はあなたの最後の例でそれを逆に得たと言います:mainは不純です(ファイルを読み書きするため)convertのようなコア関数は純粋です。

7
Andres F.

なぜはIO不純ですか?異なる時点で異なる値を返す可能性があるためです。時間に依存して必須が考慮され、遅延評価では、これはさらに重要です。次のプログラムを検討してください。

main = do  
    putStrLn "Please enter your name"  
    name <- getLine
    putStrLn $ "Hello, " ++ name

IOモナドがないと、最初のプロンプトが出力されるのはなぜですか?それに依存するものは何もないので、遅延評価は要求されないことを意味します。また、プロンプトを強制的に出力するものもありません。入力が読み取られる前。コンピュータに関する限り、IOモナドがない場合、これらの最初の2つの式は互いに完全に独立しています。幸い、nameは順序を強制します。 2番目の2つ。

順序の依存関係の問題を解決する方法は他にもありますが、IOモナドを使用すると、(言語の観点からは)おそらくすべてが遅延機能の中にとどまることができる最も簡単な方法です。命令コードの小さなセクションのないレルム。これはまた、最も柔軟性があります。たとえば、ユーザー入力に基づいて、実行時に動的にIOパイプラインを動的に動的に構築できます。

3
Karl Bielefeldt

関数型プログラミングについての私の(限られた)理解は、状態/副作用を最小限に抑え、ステートレスロジックから分離する必要があることです。

これは関数型プログラミングだけではありません。これは通常、どの言語でも良い考えです。単体テストを行う場合、read_file()がはるかに離れているにもかかわらず、convert()write_file()、およびconvert()を分割する方法は完全に自然になります。コードの最も複雑で最大の部分であるコードのテストを書くのは比較的簡単です。設定する必要があるのは入力パラメーターだけです。 read_file()write_file()のテストを書くのは(関数自体はほとんど取るに足らないことではありますが)かなり難しいです。関数を呼び出す。理想的には、そのような関数を非常に単純にして、それらをテストしないことに抵抗を感じないようにして、多くの手間を省くことができます。

PythonとHaskellの違いは、Haskellには関数に副作用がないことを証明できる型チェッカーがあるということです。Pythonでは、ファイルの読み取りまたは書き込み関数を誤ってconvert()にドロップした(たとえば、read_config_file())。Haskellでは、convert :: String -> Stringまたは同様のものを宣言し、IOモナド、型チェッカーは、これがその入力パラメーターのみに依存する純粋な関数であることを保証します。誰かがconvertを変更して構成ファイルを読み取ろうとすると、コンパイラーエラーがすぐに表示されます。それらは関数の純粋さを壊します(そして、うまくいけば、それらはconvertからread_config_fileを移動してその結果をconvertに渡し、純粋さを維持するのに十分に賢明であろう。)

2
cjs