(2009.5.12) ページを分割し、加筆。
(2017.3) 全体的に書き直し。今どきの内容に更新。
(2017.6) 例外まわりを書き直し, withFileの説明を追加。
Haskellでファイルを読み書きしたりする方法について。例外についても、少し解説。
(2017.3 追加)
Haskell のファイルIOでは、ファイルを開くときに、テキストモードとバイナリモードがある。
テキストモードでは, Windows環境では, 読み込み時に CRLF -> LF変換、書き込み時に逆の変換が行われる。UNIX 環境では、テキストファイルの改行コードが CRLFであっても、特に何も変換されない。
テキストモードでは, ファイルの文字コードが設定される。Haskell の文字列は Unicodeだった。ファイルを読み込むときに, ファイルの文字コードに沿って Unicode テキストに変換され, String
などに格納される。
ファイルを開いた時点では, 文字コードは, 実行時のロケールのそれと仮定される. ハンドルごとに, hSetEncoding
関数で変更できる。
テキストファイルを軽く扱うような場合はテキストモードを使う。
一方、バイナリモードでは、改行コードの変換は行われず、ファイルはただの8bitのバイト列として取り扱われる。
テキストだけれども文字コードが不明など難しい状況を扱うときにも, バイナリモードを用いる。
バイナリモードのファイルを読み込むときは, ByteString
を使うこと。型としては String で読み込むこともできてしまうが、1バイトずつ Char に格納されてしまう。
簡単にテキストファイルを読み込むには、Prelude モジュールの readFile
関数を呼び出します。型はこう;
readFile
の第一引数はファイル名で、ファイルを開くところからしてくれます。実際の読み込みは遅延評価され、後工程で必要でない部分は読み込まれません。
ただ, String型は廃れているので, 新しいプログラムでは Text型のほうがいいでしょう。 バイナリモードでバイト列として読み込むには, ファイルに書き込むには, 次の例は単にファイルの内容を表示します。
次の例はファイルの先頭5行のみを表示します。 ファイルを開くときに読み書きモードにしたり, 1行ずつ自分で制御したいときなどは System.IO モジュールの関数を用います。C++での <cstdio> 相当です。
ちなみに、より細かく制御したいときは, unix パッケージの System.Posix.IO モジュールを使います。UNIX (POSIX) の 話を戻して, 名前からだいたい何をするものか見当がつきます。
ファイルを開くのに, 型は同じです。
このほか, 一時ファイルを扱うために, テキストモードのときは, System.IO モジュールにある String を返す関数 (古い、非推奨) か、Text 型を返す Data.Text.IO モジュールの同名関数を使います。
バイナリモードのときは, Data.ByteStringモジュールの, ByteString を返す関数を使うこと。
1行読み込むのは EOFに達していてさらに呼び出すと, 例外が発生します。
次の例は, テキストファイルから1行ずつ読み込み、単に表示します。
ファイルが見つからないときは実行時エラー (例外) が投げられます。例外は ただし, 例外が発生したときは, 第1引数のなかの処理が中断され, 第2引数の関数が呼び出されて、 例外が発生した箇所には復帰しません。
Java などの try 〜 catch 構文と対応します. (finally も含め, 例外そのものについては, 別ページ: Haskell の例外処理)
次の例は、ファイルが見つからなかったらエラーメッセージを表示します。
(2017.3) 最近のGHCでのコンパイルエラーを修正。
(1) (2) コマンドの引数が与えられていないときは標準入力から読み込み、引数があるときはそのファイルを読み込みます。
この例は作為的で、普通なら ファイルの入出力の際, ファイルハンドルの閉じ忘れに気をつけないといけません。
例外が発生したとしても自動的にハンドルを閉じてくれる こういう定義です;
例を書きます; 複数ファイルの場合は、入れ子にしてやればOK.
Data.ByteString
モジュールの同名関数を使います。
writeFile
関数を呼び出します。宣言;
readFile
で全部読んでいるように見えますが、遅延評価のため、実際には必要でない部分は読み込まれません。
ファイルハンドル
readFile
, writeFile
は大雑把すぎます。
open
(2) 相当です。ここでは割愛します。
ファイルを開く
openFile
関数と openBinaryFile
があります。前者がテキストモード, 後者がバイナリモードになります。
openTempFile
関数, openBinaryTempFile
関数があります。
ファイルの読み込み
hGetLine
関数です。改行コードの '\n' は、読み込んだ文字列から削除され, 戻り値には含まれません。そのため, ファイル末尾の '\n' の有無を区別できません。
例外の捕捉
catch
関数で捕まえます。
Control.Exception.Safe
モジュールで, catch
関数が宣言されている。
Note.
[2017-06] 新しいコードでは, これまでの Control.Exception モジュール (baseパッケージ) ではなく, Control.Exception.Safe モジュール (safe-exceptions パッケージ) を使うようにします。
Control.Exception.Safe.catch
関数の型は、
MonadCatch
型クラスは, Control.Monad.Catch.MonadCatch
です。
catch
の第一引数が例外が発生するかもしれない処理 (本体部分)、第2引数がエラーハンドラ関数です。
catch
の戻り値はエラーハンドラの戻り値になります。
catch
の第2引数として渡すために, onError
を定義しています。戻り値の型は、値を利用するため, catch
の第1引数に合わせます. readFile
が型 IO String
なので、同じにします。
getArgs
関数はコマンドライン引数の配列を返します。
putStr
も例外が発生するかもしれない処理のなかに書くところです。
withFile
関数System.IO.withFile
関数を使います。内部で bracket
を利用しています。
withFile :: FilePath -> IOMode -> (Handle -> IO r) -> IO r
withFile name mode = bracket (openFile name mode) hClose