(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関数が宣言されている。
[2017-06] 新しいコードでは, これまでの Control.Exception モジュール (baseパッケージ) ではなく, Control.Exception.Safe モジュール (safe-exceptions パッケージ) を使うようにします。
Note.
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