関数に副作用があるとします。生成するすべての効果を入力および出力パラメーターとして受け取る場合、関数は外界に対して純粋です。
だから、不純な機能のために
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)
)
私たちはずっとモナドを使っていました
では、何をしたか見てみましょう。
(~~~) :: IO b -> (b -> IO c) -> IO c
2つの不純な関数をつなぐ演算子を定義しました
impurify :: a -> IO a
純粋な値を不純に変換する関数を定義しました。
今、私たちは、識別を行う(>>=) = (~~~)
とreturn = impurify
し、参照してください?モナドを持っています。
テクニカルノート
それが本当にモナドであることを保証するために、チェックする必要があるいくつかの公理がまだあります:
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
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
f >>= (\x -> g x >>= h) = (f >>= g) >>= h
運動のままに。