The Haskell 98 Report
top | back | next | contents | function index


7  基本 I/O

Haskell の I/O システムは純粋に関数的である。それでもなお、従来のプロ グラミング言語がもつ表現力のすべてを持っている。これを実現するために Haskell はモナドを用いて I/O 操作を純粋な関数的文脈のなか に統合している。

Haskell が用いる I/O モナドは、関数型言語では自然なと、I/O および一般的には命令型プログラミングを特徴付ける動作との間を とりもつ。Haskell での式の評価順はデータの依存性にのみ制約を受ける。 実装ではこの順序を選択するのはかなり自由である。しかしながら、動作 -- 特に I/O -- はプログラムの実行を意味のあるものにするために、妥当に定 義されたやりかたで順序付けられなければならない。Haskell の I/O モナド はユーザに動作の一列にならんだ連なりを指定する方法を用意している。実 装はこの順序を保存しなければならない。

モナドという術語は圏論として知られる数学の一分野か らのものである。しかしながら、Haskell プログラマの視点からいえば、モ ナドはひとつの抽象データ型と考えるのがベストである。I/O モナドの場合 には、その抽象的値は上でのべた動作である。いくつかの演算は、 プリミティブな動作であり、従来の I/O 操作と対応している。特別な演算 (Monad のメソッド、6.3.6 節をみ よ)が、動作を一列につなげる。これは命令型言語の一列化操作子 (セミコロ ンなど)に対応している。

Haskell では、IO ライブラリに定義されているように、非常に洗 練された I/O 装置が用意されているが、おおくの Haskell プログラムはプ レリュードがエクスポートするいくつかの単純な関数をもちいて書くことが できる。これらについてはこの節で解説する。

ここで定義されているすべての I/O 関数は文字指向である。改行文字はシス テムによって変化する。たとえば、入力の二つの文字、リターンとラインフィー ドは一つの改行文字として読まれることもある。これらの関数はバイナリ I/O に可搬的に使用することはできない。

以下の例のなかで、String[Char] の型エイリアスであ ることを思い出すこと(6.1.2 節)。

出力関数

これらの関数は標準出力デバイス(通常はユーザの端末)に書き出す。

  putChar  :: Char -> IO ()
  putStr   :: String -> IO ()
  putStrLn :: String -> IO ()  -- adds a newline
  print    :: Show a => a -> IO ()

print 関数は任意の印字可能型の値を標準出力デバイスに出力する。 印字可能型はとは Show クラスのインスタンスの型のことである。 printshow 演算を使って値を変換し、改行をくわえ て文字列にする。

たとえば、最初の20個の整数をおよびその2乗を印字するプログラムは以下の ように書くことができる。

main = print ([(n, 2^n) | n <- [0..19]])

入力関数

これらの関数は標準入力(通常、ユーザ端末)から入力を読む。

  getChar     :: IO Char
  getLine     :: IO String
  getContents :: IO String
  interact    :: (String -> String) -> IO ()
  readIO      :: Read a => String -> IO a
  readLn      :: Read a => IO a

getChar および getLine の両方は EOF で例外 (7.3 節)を発生する。この例外を 同定する述語 isEOFErrorIO ライブラリで定義され ている。getLine 演算は hGetLine と同じ状況で例外を 発生させる。これも IO ライブラリで定義されている。

getContents 演算はすべてのユーザ入力をひとつの文字列として返 す。この文字列は必要になったときに遅延して読まれる。interact 関数は String->String 型の関数を引数としてとる。標準入力 からの入力全体がこの関数に渡され、結果の文字列を標準出力デバイスに出 力する。

典型的には、Read クラス の read 演算を文字列から値 に変換するのに用いる。readIO 関数は、プログラムを停止させる 代りに、I/O モナドにパーズ失敗のシグナルをあげることを除けば read と類似している。readLn 関数は getLinereadIO とを組合せる。

以下のプログラムは単純に標準入力からすべての非ASCII文字を取り除き、結 果を標準出力にエコーする。(isAscii 関数はライブラリで定義されている。)

main = interact (filter isAscii)

ファイル

これらの関数はキャラクタファイルを操作する。ファイルはなんらかの文字 列をファイル名として解決する実装指定の方法で文字列によって名前がつけ られている。

writeFile および appendFile 関数は第二引数の文字列 を、第一引数のファイルに書く、あるいは、追加する。readFile 関数はファイルを読みそのファイルの内容を文字列として返す。このファイ ルは遅延され、必要に応じて getContents で読まれる。

  type FilePath =  String
  
  writeFile  :: FilePath -> String -> IO ()
  appendFile :: FilePath -> String -> IO ()
  readFile   :: FilePath           -> IO String

writeFile および appendFile はリテラル文字列をファ イルに書き込むことに注意せよ。印字可能型の値を書くためには print と同様に、まず、値を文字列へ変換するのに show 関数を用いる。

main = appendFile "squares" (show [(x,x*x) | x <- [0,0.1..2]])

7.2  I/O 操作の直列化

型構成子 IOMonad クラスのインスタンスである。モ ナドのふたつのバインディング関数、すなわち Monad クラスのメソッドは I/O 操作のならびを合成するのに用いる。>> 関数は最初の 操作の結果に興味がないというところで、たとえば、その結果が () であるような場合に用いられる。>>= 演算は最 初の操作の結果を次の操作へ引数として渡す。

  (>>=) :: IO a -> (a -> IO b) -> IO b 
  (>>)  :: IO a -> IO b        -> IO b

たとえば、

main = readFile "input-file"                       >>= \ s ->
       writeFile "output-file" (filter isAscii s)  >>
       putStr "Filtering successful\n"

は、先の例 interact を用いた例ににているが、これは "input-file" からの入力をとり、その出力を "output-file" に書きだす。メッセージはプログラムが終了する前 に標準出力に印字される。

do 記法はより命令型にちかい構文でプログラムすることを可能に する。先の例よりいくぶん優雅なバージョンは以下のようになる。

main = do
        putStr "Input file: "
        ifile <- getLine
        putStr "Output file: "
        ofile <- getLine
        s <- readFile ifile
        writeFile ofile (filter isAscii s) 
        putStr "Filtering successful\n"

return 関数は I/O 操作の結果を定義するのに用いられる。たとえ ば、getLinereturn を用いて結果を定義して getChar を使って定義される。

7.3  I/O モナドの例外処理

I/O モナドは単純な例外処理機構をもっている。すべての I/O 操作は結果を 返さず、例外を発生することがある。

I/O モナドの例外は、型 IOError 型の値として表現される。これ は抽象型で、その構成子はユーザからは隠蔽されている。IO ライ ブラリは IOError の値を構成したり、検証したりする関数を定義 している。IOError の値を生成する唯一のプレリュード関数は userError である。ユーザエラーの値はそのエラーを説明する文字 列を含む。

  userError :: String -> IOError

Exceptions are raised and caught using the following functions:

  ioError :: IOError -> IO a
  catch   :: IO a    -> (IOError -> IO a) -> IO a 

ioError 関数は例外を発生する。catch 関数は、それに より保護された動作内で発生した任意の例外を受けとるハンドラを確立する。 例外は catch により確立されたもっとも近いハンドラによって捕 捉される。これらのハンドラは選択することはなく、すべての例外が捕捉さ れる。例外の伝播は、ハンドラ内で任意の望まぬ例外を再発生することで明 示的に用意されなればならない。たとえば、

f = catch g (\e -> if IO.isEOFError e then return [] else ioError e)

において関数 f は、g 内で EOF 例外がおこったとき、 [] を返す。そうでなければ、例外は次の外側のハンドラへ伝播す る。isEOFError 関数は IO ライブラリの一部である。

例外がメインプログラムの外側に伝播したとき、Haskell システムはそれに 対応した IOError の値を印字し、そのプログラムをぬける。

Monad クラス(6.3.6 節)の IO インスタンスの fail メソッドは userError をひき起す。すなわち

  instance Monad IO where 
    ...bindings for return, (>>=), (>>)

    fail s = ioError (userError s)

プレリュード中の I/O 関数によって惹き起こされた例外は 21 章で定義されている。


The Haskell 98 Report
top | back | next | contents | function index
December 2002