副作用を処理するためのIOモナドパターンの利点は純粋にアカデミックですか?


17

さらに別のFP +副作用の質問で申し訳ありませんが、私にこれに完全に答える既存の質問を見つけることができませんでした。

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

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

私はこのパターンを理解しようとしていますが、実際には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,
)

その後、実際にそれらのステップを呼び出すのではなく、インタープリターにそれを行わせることで、責任を放棄しますか?

または別の言い方をすれば、それは次のように書くことです:

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モナドに含まれることになります。

コードが実際に実行されると、ファイルを読み込んだ後のすべてがそのファイルの状態に依存するため、命令型実装と同じ状態関連のバグに苦しむことになります。

私はステートフルな振る舞いを減らし分離することの利点を完全に感謝しています。それが実際に命令型バージョンをそのように構成した理由です。convert()完全に純粋であり、キャッシュ可能性、スレッドセーフなどの利点を享受できることを願っています。

また、モナド型は、特に同等の型で動作するパイプラインで役立つことがありますが、既にそのようなパイプラインにない限り、IOがモナドを使用する理由がわかりません。

IOモナドパターンがもたらす副作用に対処するための追加の利点はありますか?


1
このビデオを見ください。 モナドの不思議は、カテゴリー理論やHaskellに頼ることなく最終的に明らかになります。モナドはJavaScriptで簡単に表現されており、Ajaxの重要なイネーブラーの1つであることがわかりました。モナドは素晴らしいです。それらは単純なもので、ほとんど自明で実装されており、複雑さを管理する大きな力を持っています。しかし、それらを理解することは驚くほど難しく、ほとんどの人は、そのah-haの瞬間を経験すると、他の人に説明する能力を失うようです。
ロバートハーヴェイ

良いビデオ、ありがとう。私は実際に関数型プログラミングのJSイントロからこのようなことを学びました(そしてさらに100万を読んで…)。それを見たことはありますが、私の質問はIOモナドに固有のものであると確信しています。Crockはこのビデオでは取り上げていません。
スチュコックス

うーん... AJAXはI / Oの形式とは見なされませんか?
ロバートハーベイ

1
mainHaskellプログラムのタイプはIO ()IOアクションであることに注意してください。これは実際には関数ではありません。それはだ。プログラム全体は、言語ランタイムに何をすべきかを指示する命令を含む純粋な値です。すべての不純なもの(実際にIOアクションを実行する)は、プログラムの範囲外です。
ワイザード

この例では、モナド部分は、ある計算の結果(read_file)を取得し、それを次の計算の引数()として使用する場合write_fileです。一連の独立したアクションだけがあれば、Monadは必要ありません。
lortabac

回答:


14

プログラム全体は、基本的に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

これを見ていない、彼らはそうconvertでは「含まれる」されてIO、それがされるように、ではなく、孤立からIO。そのタイプからconvertIOアクションで発生するものに依存することはありません。

コードが実際に実行されると、ファイルを読み込んだ後のすべてがそのファイルの状態に依存するため、命令型実装と同じ状態関連のバグに悩まされます。

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

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

これはやや抽象的な点ですが、Haskellerがこれについて話すときの意味の鍵となります。任意の有効な引数convertが与えられると、その引数に対して正しい結果が生成されるように書きたいと思います。そのように見ると、ファイルの読み取りがステートフルな操作であるという事実は、方程式には入りません。重要なのは、どんな引数がそれに与えられ、どこから来たとしても、それを正しく処理しなければならないということです。そして、純度がその入力でできることを制限するという事実は、その推論を単純化します。convertconvert

そのconvertため、一部の引数から誤った結果が生成され、readFileそのような引数が渡された場合、stateによって導入されたバグとは見なされません。これは純粋な関数のバグです!


これは最高の説明だと思います(他の人も私のために物事を明確にするのに役立ちましたが)、ありがとう。
スチュコックス

pythonにはモナドを使用すると、(静的な)型が1つしかないため、何の保証もないため、Pythonでモナドを使用してもメリットが少ないことに注意してください。
jk。

7

「純粋にアカデミック」という言葉の意味を正確に確認するのは難しいですが、答えはほとんど「いいえ」だと思います。

サイモン・ペイトン・ジョーンズによる「厄介な部隊への取り組み」(読むことを強くお勧めします!)で説明したように、モナドI / Oは、HaskellがI / Oを処理する方法に関する実際の問題を解決することを目的としていました。ここではコピーしませんが、リクエストとレスポンスがあるサーバーの例を読んでください。それは非常に有益です。

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

これは、アカデミックなモナドI / Oがどのようになっているのかというよりも、HaskellとPythonの異なる哲学について述べていることに注意してください。Pythonには使用しません。

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

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

Haskell main関数がで「生きている」のは事実ですIOが、実際のHaskellプログラムは、IO必要でないときは使用しないことをお勧めします。I / Oを実行する必要のないほとんどすべての関数は、typeを持つべきではありませんIO

あなたの最後の例では、あなたはそれを逆方向に持っていると言いたい:(mainファイルを読み書きするため)不純ですが、中核的な機能convertは純粋です。


3

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

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

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

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


2

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

それは単なる関数型プログラミングではありません。通常、それはどの言語でも良い考えです。あなたはユニットテスト、あなたは離れて分割する方法を行う場合はread_file()convert()write_file()にもかかわらず、ので、自然に完全に来てconvert()、これまでで、コードの中で最も複雑かつ最大の一部であること、そのためのテストを書くことは比較的容易である:あなたが設定する必要があるすべては、入力パラメータであります。関数の呼び出しの前後にファイルシステム上で物事を作成したり読み込んだりする必要があるため、テストの作成read_file()とテストwrite_file()はかなり困難です(関数自体はほとんど自明ですが)。理想的には、そのような関数を非常に単純にして、テストせずに快適に感じるようにして、面倒な作業を省くようにします。

PythonとHaskellの違いは、Haskellには型チェッカーがあり、関数に副作用がないことを証明できることです。Pythonではconvert()、(たとえばread_config_file())にファイルの読み取りまたは書き込み機能が誤ってドロップされないことを期待する必要があります。Haskell では、モナドconvert :: String -> Stringを使用せずに宣言または類似する場合IO、型チェッカーは、これが入力パラメーターのみに依存し、それ以外に依存しない純粋な関数であることを保証します。構成convertファイルを読み取るために変更しようとすると、関数の純度を損なうことを示すコンパイラエラーがすぐに表示されます。(そして、うまくいけば、純度を維持しながら、結果read_config_fileから抜け出しconvert、結果を渡すのに十分なほど賢明であることを願っていますconvert。)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.