Prev: List モナド TOC: 目次 Next: State モナド

The IO monad


概観

計算のタイプ: I/O を実行する計算
束縛の戦略: I/O 動作はそれらが束縛された順番に実行される。 失敗すると I/O エラーが投げられ、それを捕捉、処理することができる
利用場面: Haskell のプログラム内で I/O を実行する
ゼロおよびプラス: なし
型の例: IO a

動機

入出力は、参照透明ではなく、副作用があるので、純粋な関数型言語とは 相容れないものです。IO モナドはこの問題を、入出力の実行を IO モナド内に 閉じ込めることで解決します。

定義

IO モナドの定義はプラットフォーム依存です。データ構築子もエクスポート されていなければ、IO モナドからデータを取り除くための関数も提供されて いません。というわけで、IO モナドは一方向モナドであり、 副作用および非参照透明な動作を IO モナド命令型スタイルの計算に隔離する ことで、関数プログラムの安全性を確保するのに不可欠です。

このチュートリアルを通じて、モナド値を計算と呼びならわしてきましたが、 IO モナドの値は、しばしば I/O 動作 と呼ばれます。ここでは こちらの用語を使います。

Haskell では、トップレベルの main 関数の型は IO () でなければなりません。したがって、プログラムは トップレベルでは、命令型スタイルの入出力動作の並びとして構成され、 それが関数型スタイルのコードを呼ぶことになります。IO モジュールからエクスポートされている関数は、入出力そのものを 実行するわけではありません。入出力動作を返し、そこに実行すべき 入出力操作が記述されています。入出力動作は IO モナド内で(純粋に 関数的に)合成され、さらに複雑な入出力動作を生成します。その結果として 最終的な入出力動作は、そのプログラムの main 関数の値と なります。

標準プレリュードと IO module では、IO モナド中で使える多くの関数が定義されています。Haskell の プログラマであれば、まちがいなく、そのうちいくつかには慣れていること でしょう。このチュートリアルでは、IO モナドのモナドとしての側面について 議論し、入出力を実行する関数のすべてについては議論しません。

IO 型構築子は Monad クラスおよび MonadError クラスのメンバーです。そのエラーの型は IOErrorです。fail は文字列引数から構成された エラーを投げるように定義されています。IO モナド内では、 Control.Monad.Error モジュールをインポートしていれば、 そのモナドテンプレートライブラリで例外機構を使うことができます。 同じ機構は IO モジュールでエクスポートされている ioError および catch ででも可能です。

instance Monad IO where
    return a = ...   -- function from a -> IO a
    m >>= k  = ...   -- executes the I/O action m and binds the value to k's input  
    fail s   = ioError (userError s)

data IOError = ...

ioError :: IOError -> IO a
ioError = ...
   
userError :: String -> IOError
userError = ...

catch :: IO a -> (IOError -> IO a) -> IO a 
catch = ...

try :: IO a -> IO (Either IOError a)
try f = catch (do r <- f
                  return (Right r))
              (return . Left)

IO モナドはモナドテンプレートライブラリフレームワークに MonadError クラスのインスタンスとして組込まれています。

instance Error IOError where
  ...

instance MonadError IO where
    throwError = ioError
    catchError = catch

IO モジュールは try という便利な関数を エクスポートしています。この関数は入出力動作を実行し、その動作が 成功すれば、Right result を、入出力エラーが捕捉されれば、 Left IOError を返します。

この例は、標準入力ストリームを標準出力ストリームに、コマンドライン引数 にしたがって、文字変換を行いつつ複写する、"tr" コマンドの部分的な実装 です。これは、IO モナドでの MonadError の 例外処理機構の使いかたを例示したものです。

example14.hs で使えるコード
import Monad
import System
import IO
import Control.Monad.Error

-- set1 内の文字と対応する set2 内の文字に変換する
translate :: String -> String -> Char -> Char
translate []     _      c = c
translate (x:xs) []     c = if x == c then ' ' else translate xs []  c
translate (x:xs) [y]    c = if x == c then  y  else translate xs [y] c
translate (x:xs) (y:ys) c = if x == c then  y  else translate xs ys  c

-- 文字列全体を変換する
translateString :: String -> String -> String -> String
translateString set1 set2 str = map (translate set1 set2) str

usage :: IOError -> IO ()
usage e = do putStrLn "Usage: ex14 set1 set2"
             putStrLn "Translates characters in set1 on stdin to the corresponding"
             putStrLn "characters from set2 and writes the translation to stdout."

-- コマンドライン引数にしたがって、標準入力を標準出力へ変換する
main :: IO ()
main = (do [set1,set2] <- getArgs
           contents    <- hGetContents stdin
           putStr $ translateString set1 set2 contents)
       `catchError` usage

Prev: List モナド TOC: 目次 Next: State モナド