183

Haskellの不純な計算がモナドとしてモデル化されている理由について誰かがいくつかの指針を与えることができますか?

つまり、モナドは4つの操作を備えた単なるインターフェースなので、その中で副作用をモデル化する理由は何でしたか?

4

8 に答える 8

301

関数に副作用があるとします。それが生成するすべての効果を入力および出力パラメーターとして使用すると、関数は外界に対して純粋になります。

だから、不純な機能のために

f' :: Int -> Int

RealWorldを検討に追加します

f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.

その後、f再び純粋です。パラメータ化されたデータ型を定義するtype IO a = RealWorld -> (a, RealWorld)ので、RealWorldを何度も入力する必要はなく、次のように記述できます。

f :: Int -> IO Int

プログラマーにとって、RealWorldを直接処理することは危険すぎます。特に、プログラマーがRealWorld型の値を入手した場合、それをコピーしようとする可能性がありますが、これは基本的に不可能です。(たとえば、ファイルシステム全体をコピーしようと考えてください。どこに配置しますか?)したがって、IOの定義は、全世界の状態もカプセル化します。

「不純な」機能の構成

これらの不純な関数は、それらを連鎖させることができなければ役に立たない。検討

getLine     :: IO String            ~            RealWorld -> (String, RealWorld)
getContents :: String -> IO String  ~  String -> RealWorld -> (String, RealWorld)
putStrLn    :: String -> IO ()      ~  String -> RealWorld -> ((),     RealWorld)

したい

  • コンソールからファイル名を取得し、
  • そのファイルを読んで、
  • そのファイルの内容をコンソールに出力します

現実世界の州にアクセスできるとしたら、どうすればよいでしょうか。

printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 -- results in ((), world3)

ここにパターンがあります。関数は次のように呼び出されます。

...
(<result-of-f>, worldY) = f               worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...

~~~したがって、それらをバインドする演算子を定義できます。

(~~~) :: (IO b) -> (b -> IO c) -> IO c

(~~~) ::      (RealWorld -> (b,   RealWorld))
      ->                    (b -> RealWorld -> (c, RealWorld))
      ->      (RealWorld                    -> (c, RealWorld))
(f ~~~ g) worldX = let (resF, worldY) = f worldX
                   in g resF worldY

そうすれば簡単に書くことができます

printFile = getLine ~~~ getContents ~~~ putStrLn

現実の世界に触れることなく。

「浄化」

ここで、ファイルの内容も大文字にしたいとします。大文字と小文字は純粋関数です

upperCase :: String -> String

しかし、それを現実の世界にするためには、を返す必要がありIO Stringます。このような機能を解除するのは簡単です。

impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)

これは一般化できます:

impurify :: a -> IO a

impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)

だからimpureUpperCase = impurify . upperCase、そして私たちは書くことができます

printUpperCaseFile = 
    getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn

(注:通常は次のように記述しますgetLine ~~~ getContents ~~~ (putStrLn . upperCase)

私たちはずっとモナドで働いていました

今、私たちが何をしたか見てみましょう:

  1. (~~~) :: IO b -> (b -> IO c) -> IO c2つの不純な関数をつなぐ演算子を定義しました
  2. impurify :: a -> IO a純粋な値を不純な値に変換する関数を定義しました。

今、私たちは識別(>>=) = (~~~)を行いreturn = impurify、そして見ますか?モナドがあります。


テクニカルノート

それが本当にモナドであることを確認するために、チェックする必要のあるいくつかの公理がまだあります:

  1. return a >>= f = f a

     impurify a                =  (\world -> (a, world))
    (impurify a ~~~ f) worldX  =  let (resF, worldY) = (\world -> (a, world )) worldX 
                                  in f resF worldY
                               =  let (resF, worldY) =            (a, worldX)       
                                  in f resF worldY
                               =  f a worldX
    
  2. f >>= return = f

    (f ~~~ impurify) worldX  =  let (resF, worldY) = f worldX 
                                in impurify resF worldY
                             =  let (resF, worldY) = f worldX      
                                in (resF, worldY)
                             =  f worldX
    
  3. f >>= (\x -> g x >>= h) = (f >>= g) >>= h

    運動として残しました。

于 2010-03-21T21:44:05.703 に答える
44

Haskellの不純な計算がモナドとしてモデル化されている理由について誰かがいくつかの指針を与えることができますか?

この質問には、広範囲にわたる誤解が含まれています。不純物とモナドは独立した概念です。不純物はモナドによってモデル化されていません。IOむしろ、命令型計算を表す、などのいくつかのデータ型があります。そして、それらのタイプのいくつかでは、それらのインターフェースのごく一部が「モナド」と呼ばれるインターフェースパターンに対応しています。さらに、の意味について一般的に言われている話はありますが、純粋/機能的/外延的な説明は知られていません(そして、の「シンビン」の目的をIO考慮すると、説明がない可能性があります) 。その話は正直に説明することはできません。IOWorld -> (a, World)IO aIOIO並行性と非決定性をサポートします。計算中の世界との相互作用を可能にする決定論的計算の場合でも、この話は機能しません。

詳細については、この回答を参照してください。

編集:質問を読み直したところ、私の答えは順調に進んでいないと思います。質問が言ったように、命令型計算のモデルはしばしばモナドであることがわかります。質問者は、モナドネスが命令型計算のモデリングを可能にすることを実際には想定していない可能性があります。

于 2011-08-16T00:08:54.950 に答える
13

私が理解しているように、Eugenio Moggiと呼ばれる人は、「モナド」と呼ばれる以前はあいまいだった数学的構成がコンピューター言語の副作用をモデル化するために使用できることに最初に気づきました。したがって、ラムダ計算を使用してそれらのセマンティクスを指定します。Haskellが開発されていたとき、不純な計算をモデル化するさまざまな方法がありました(詳細については、Simon Peyton Jonesの「ヘアシャツ」ペーパーを参照してください)が、Phil Wadlerがモナドを導入したとき、これが答えであることがすぐに明らかになりました。そして残りは歴史です。

于 2010-03-22T07:54:22.430 に答える
8

Haskellの不純な計算がモナドとしてモデル化されている理由について誰かがいくつかの指針を与えることができますか?

ええと、Haskellは純粋だからです。純粋でない計算とタイプレベルの純粋な計算を区別し、それぞれのプログラムフローをモデル化するには、数学的な概念が必要です。

IO aこれは、不純な計算をモデル化するタイプになってしまうことを意味します。次に、これらの計算を組み合わせて順番に適用する方法(>>=)を理解し、値を持ち上げるreturn)が最も明白で基本的な方法である必要があります。

これらの2つを使用して、モナドを(考えもせずに)すでに定義しました。)

さらに、モナドは非常に一般的で強力な抽象化をsequence提供するため、多くの種類の制御フローを、などのモナド関数liftMや特別な構文で便利に一般化でき、不純さをそのような特別な場合ではありません。

詳細については、関数型プログラミング一意性タイピング(私が知っている唯一の選択肢)のモナドを参照してください。

于 2010-03-21T21:18:37.237 に答える
7

あなたが言うように、それMonadは非常に単純な構造です。答えの半分は次のとおりです。Monad副作用のある関数に与えることができ、それらを使用できる最も単純な構造です。これによりMonad、2つのことができます。純粋な値を副作用値(return)として扱うことと、副作用関数を副作用値に適用して新しい副作用値(>>=)を取得することです。これらのいずれかを実行する能力を失うことは壊滅的であるため、副作用タイプは「少なくとも」MonadであるMonad必要があり、これまでに必要なすべてを実装するのに十分であることがわかります。

残りの半分は、「起こりうる副作用」に与えることができる最も詳細な構造は何ですか?考えられるすべての副作用のスペースをセットとして考えることができます(必要な操作はメンバーシップのみです)。2つの副作用を次々に実行することで組み合わせることができます。これにより、異なる副作用が発生します(または、同じものである可能性があります。最初の副作用が「コンピューターのシャットダウン」で、2番目の副作用が「ファイルの書き込み」の場合、結果はこれらを構成するのは、単なる「シャットダウンコンピュータ」です)。

では、この操作について何と言えますか?それは連想的です。つまり、3つの副作用を組み合わせる場合、どの順序で組み合わせるかは関係ありません。コンピュータをシャットダウンする場合(ファイルを書き込んでからソケットを読み取る)、ファイルを書き込んでからシャットダウンする場合と同じです(ソケットを読み取ってからシャットダウンします)。コンピューター)。ただし、可換ではありません。(「ファイルの書き込み」、「ファイルの削除」)は、(「ファイルの削除」、「ファイルの書き込み」)とは異なる副作用です。そして、私たちにはアイデンティティがあります。特別な副作用「副作用なし」が機能します(「副作用なし」、「ファイルの削除」は「ファイルの削除」と同じ副作用です)この時点で、数学者は「グループ」と考えています。しかし、グループには逆数があり、一般的に副作用を逆にする方法はありません。"ファイルを削除する" 不可逆的です。したがって、私たちが残した構造はモノイドの構造です。つまり、副作用関数はモナドでなければなりません。

より複雑な構造はありますか?もちろん!考えられる副作用をファイルシステムベースの効果、ネットワークベースの効果などに分割し、これらの詳細を保持するより複雑な構成ルールを考え出すことができます。しかし、繰り返しになりますMonadが、非常にシンプルでありながら、私たちが関心を持っているほとんどのプロパティを表現するのに十分強力です。(特に、結合性と他の公理により、アプリケーションを小さな断片でテストできます。組み合わせたアプリケーションの副作用は、断片の副作用の組み合わせと同じになると確信しています)。

于 2014-09-30T01:08:46.107 に答える
4

これは、実際には、I/Oを機能的に考えるための非常にクリーンな方法です。

ほとんどのプログラミング言語では、入出力操作を行います。Haskellで、操作を実行するのではなく、実行したい操作のリストを生成するコードを書くことを想像してみてください。

モナドはまさにそのためのかなりの構文です。

モナドが他のものとは対照的である理由を知りたい場合、答えは、モナドがHaskellを作成しているときに人々が考えることができるI/Oを表すための最良の機能的な方法であるということだと思います。

于 2010-03-22T14:44:03.907 に答える
3

AFAIK、その理由は、型システムに副作用チェックを含めることができるようにするためです。詳細を知りたい場合は、これらのSE-Radioエピソードを聞いてください:エピソード108:関数型プログラミングのSimon Peyton JonesとHaskellエピソード72:LINQのErik Meijer

于 2010-03-21T21:18:56.083 に答える
2

Above there are very good detailed answers with theoretical background. But I want to give my view on IO monad. I am not experienced haskell programmer, so May be it is quite naive or even wrong. But i helped me to deal with IO monad to some extent (note, that it do not relates to other monads).

First I want to say, that example with "real world" is not too clear for me as we cannot access its (real world) previous states. May be it do not relates to monad computations at all but it is desired in the sense of referential transparency, which is generally presents in haskell code.

So we want our language (haskell) to be pure. But we need input/output operations as without them our program cannot be useful. And those operations cannot be pure by their nature. So the only way to deal with this we have to separate impure operations from the rest of code.

Here monad comes. Actually, I am not sure, that there cannot exist other construct with similar needed properties, but the point is that monad have these properties, so it can be used (and it is used successfully). The main property is that we cannot escape from it. Monad interface do not have operations to get rid of the monad around our value. Other (not IO) monads provide such operations and allow pattern matching (e.g. Maybe), but those operations are not in monad interface. Another required property is ability to chain operations.

If we think about what we need in terms of type system, we come to the fact that we need type with constructor, which can be wrapped around any vale. Constructor must be private, as we prohibit escaping from it(i.e. pattern matching). But we need function to put value into this constructor (here return comes to mind). And we need the way to chain operations. If we think about it for some time, we will come to the fact, that chaining operation must have type as >>= has. So, we come to something very similar to monad. I think, if we now analyze possible contradictory situations with this construct, we will come to monad axioms.

Note, that developed construct do not have anything in common with impurity. It only have properties, which we wished to have to be able to deal with impure operations, namely, no-escaping, chaining, and a way to get in.

Now some set of impure operations is predefined by the language within this selected monad IO. We can combine those operations to create new unpure operations. And all those operations will have to have IO in their type. Note however, that presence of IO in type of some function do not make this function impure. But as I understand, it is bad idea to write pure functions with IO in their type, as it was initially our idea to separate pure and impure functions.

Finally, I want to say, that monad do not turn impure operations into pure ones. It only allows to separate them effectively. (I repeat, that it is only my understanding)

于 2012-01-06T20:12:08.070 に答える