さらに申し訳ありません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である場合、ファイルを読み取った後のすべてはそのファイルの状態に依存するため、命令型実装と同じ状態関連のバグが発生するため、プログラマーとして実際に何かを得ましたか誰がこれを維持しますか?
reduceとisolatingのステートフルな動作の利点に完全に感謝します。これが実際に、私がそのような命令バージョンを構成した理由です。入力を収集し、純粋なものを実行し、出力を吐き出します。うまくいけば、convert()
は完全に純粋になり、キャッシュ可能性、スレッドセーフなどのメリットを享受できます。
特に比較可能な型で動作するパイプラインでモナディック型が役立つこともありがたく思いますが、IOがそのようなパイプライン内にない限り、なぜモナドを使用する必要があるのかわかりません。
IOモナドパターンがもたらす副作用に対処することには、他に利点がありますか?
プログラム全体は、基本的に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
だと思います。
IO
の部分よりもはるかに複雑です。IO
をまったく扱わずに、推論とテストを行うことができます。そのため、彼らはこれをconvert
がIO
に「含まれる」とは見なさず、むしろisolatedからIO
であると見なします。そのタイプから、convert
が行うことは、IO
アクションで発生することに依存することはできません。
コードが実際に実行されると、ファイルを読み取った後のすべてがそのファイルの状態に依存するため、命令型実装と同じ状態関連のバグが発生するため、これを維持するプログラマーとして実際に何かを得ましたか?
これは次の2つに分かれると思います。
convert
への引数の値は、ファイルの状態によって異なります。convert
関数が行うことは、ファイルの状態に依存しません。 convert
は常に同じ関数であり、異なる場所で異なる引数で呼び出された場合でも同様です。これはやや抽象的な点ですが、Haskellersがこれについて話すときに何を意味するのかは非常に重要です。 any有効な引数を指定してconvert
を記述したい場合、その引数に対して正しい結果が生成されます。このように見ると、ファイルの読み取りがステートフルな操作であるという事実は方程式には入りません。重要なのは、引数が与えられても、それがどこから来ても、convert
はそれを正しく処理する必要があるということです。そして、純粋さがconvert
がその入力で実行できることを制限するという事実は、その推論を単純化します。
したがって、convert
がいくつかの引数から誤った結果を生成し、readFile
がそのような引数をフィードする場合、state。純粋な関数のバグです。
「純粋に学術的」とは、あなたが何を意味するのか正確に理解するのは難しいですが、答えはほとんど「ノー」だと思います。
厄介な分隊への取り組み による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
のようなコア関数は純粋です。
なぜはIO不純ですか?異なる時点で異なる値を返す可能性があるためです。時間に依存して必須が考慮され、遅延評価では、これはさらに重要です。次のプログラムを検討してください。
main = do
putStrLn "Please enter your name"
name <- getLine
putStrLn $ "Hello, " ++ name
IOモナドがないと、最初のプロンプトが出力されるのはなぜですか?それに依存するものは何もないので、遅延評価は要求されないことを意味します。また、プロンプトを強制的に出力するものもありません。入力が読み取られる前。コンピュータに関する限り、IOモナドがない場合、これらの最初の2つの式は互いに完全に独立しています。幸い、name
は順序を強制します。 2番目の2つ。
順序の依存関係の問題を解決する方法は他にもありますが、IOモナドを使用すると、(言語の観点からは)おそらくすべてが遅延機能の中にとどまることができる最も簡単な方法です。命令コードの小さなセクションのないレルム。これはまた、最も柔軟性があります。たとえば、ユーザー入力に基づいて、実行時に動的にIOパイプラインを動的に動的に構築できます。
関数型プログラミングについての私の(限られた)理解は、状態/副作用を最小限に抑え、ステートレスロジックから分離する必要があることです。
これは関数型プログラミングだけではありません。これは通常、どの言語でも良い考えです。単体テストを行う場合、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
に渡し、純粋さを維持するのに十分に賢明であろう。)