SML/NJ の入出力は Common Lisp などの近代的なプログラミング言語と同様に「ストリーム (stream)」を介して行われます。入力ストリームを表すデータ型が instream で、出力ストリームを表すデータ型が outstream です。テキストファイルの入出力関数はストラクチャ TextIO にまとめられています。今回はファイルの入出力について説明します。
ファイルからデータを入力、逆にデータをファイルへ出力する場合、SML/NJ では「ストリーム (stream)」を使います。ストリームは「流れ」や「小川」という意味ですが、プログラミング言語の場合は「ファイルとプログラムの間でやりとりされるデータの流れ」という意味で使われています。
SML/NJ では、ストリーム型データを介してファイルにアクセスします。ストリームはファイルと一対一に対応していて、ファイルからデータを入力する場合は、ストリームを経由してデータが渡されます。逆に、ファイルへデータを出力するときも、ストリームを経由して行われます。
ファイルにアクセスする場合、次の 3 つの操作が基本になります。
「ファイルをオープンする」とは、アクセスするファイルを指定して、それと一対一に対応するストリームを生成することです。入出力関数は、そのストリームを経由してファイルにアクセスします。SML/NJ の場合、ファイルをオープンするには関数 openIn と openOut を使います。オープンしたファイルは必ずクローズしてください。この操作を行う関数が closeIn と closeOut です。
val openIn : string -> instream val openOut : string -> outstream val closeIn : instream -> unit val closeOut : outstream -> unit
ファイル名は文字列で指定し、ファイル名のパス区切り記号にはスラッシュ ( / ) を使います。\ は文字列のエスケープコードに割り当てられているため、そのままではパス区切り記号に使うことはできません。ご注意ください。ファイルのオープンやクローズに失敗した場合は例外 Io が送出されます。
主な入出力関数を次に示します。
読み込み val TextIO.input1 : instream -> char option val TextIO.inputLine : instream -> string option 書き込み val TextIO.output1 : outstream * char -> unit val TextIO.output : outstream * string -> unit
関数 input1 は入力ストリームから 1 文字 (1 byte) 読み込みます。関数 inputLine はファイルから 1 行読み込みます。改行文字は削除されません。関数 output1 は出力ストリームに 1 文字 (1 byte) 書き込みます。関数 output は出力ストリームに 1 行書き込みます。
入力ストリームの場合、ファイルに格納されているデータには限りがあるので、ストリームからデータを取り出していくと、いつかはデータがなくなります。この状態を「ファイルの終了 (end of file : EOF)」 といいます。ファイルが終了したとき、input1 と inputLine は NONE を返します。また、ファイルの終了は次の関数でチェックすることができます。
val TextIO.endOfStream : instream -> bool
ファイルが EOF の場合、endOfStream は true を返します。そうでなければ false を返します。
簡単な例題として、ファイルの内容を画面へ出力する関数 cat を作ってみましょう。input1 と output1 を使うと、プログラムは次のようになります。
リスト : ファイルの表示 (1) fun cat1 filename = let open TextIO val a = openIn( filename ) fun cat_sub NONE = () | cat_sub(SOME c) = (output1(stdOut, c); cat_sub(input1 a)) in cat_sub(input1 a); closeIn a end
関数 cat1 の引数 filename はファイル名を表す文字列です。最初にストラクチャ TextIO をオープンします。open は宣言なので、let と in の間に書くことができます。有効範囲は局所変数の場合と同じです。次に、openIn でファイルをオープンします。
ファイルの表示は関数 cat_sub で行います。cat_sub の引数は input1 a の返り値 (char option) です。NONE の場合はファイルが終了したのでユニットを返します。データがある場合は、パターンマッチングで文字 c を取り出し、output1 で c を標準出力 (stdOut) へ出力します。そして、input1 a で 1 文字読み込んで cat_sub を再帰呼び出しします。最後に、closeIn でファイルを閉じます。
cat1 は再帰呼び出しを使いましたが、繰り返しでも簡単にプログラムできます。次のリストを見てください。
リスト : ファイルの表示 (2) fun cat2 filename = let open TextIO val a = openIn filename val c = ref (NONE: char option) in while (c := input1 a; isSome (!c)) do output1(stdOut, valOf(!c)); closeIn a end
まず、option 型を格納する ref 変数 c を用意します。NONE は多相的なデータなので、型を char option に指定します。while ループの条件部は複文を使っています。最初に、ref 変数 c に input1 の返り値をセットし、次に関数 isSome でデータがあるかチェックします。これが複文の返り値になるので、isSome が false を返すと while ループが終了します。データがある間は output1 でデータを stdOut へ出力します。
次は、inputLine と output を使ってみましょう。プログラムは次のようになります。
リスト : ファイルの表示 (3) fun cat3 filename = let open TextIO val a = openIn filename val b = ref (NONE: string option) in while (b := inputLine a; isSome (!b)) do output(stdOut, valOf(!b)); closeIn a end fun cat4 filename = let open TextIO val a = openIn filename in while (not(endOfStream a)) do output(stdOut, valOf(inputLine a)); closeIn a end
関数 cat3 は cat2 を行単位の入出力に改造しただけです。string option を格納する ref 変数 b を用意します。while ループの条件部で、inputLine の返り値を b にセットし、isSome でデータがあるかチェックします。NONE であれば while ループを終了します。そうでなければ、データを output で stdOut へ出力します。
関数 cat4 は endOfStream でファイルの終了をチェックしています。endOfStream はファイルが終了すると true を返すので、while ループの条件部では述語 not で結果を反転していることに注意してください。あとは inputLine で読み込んだデータを output で stdOut へ出力するだけです。
データをファイルに書き込むには、ファイルを openOut でオープンします。このとき、注意事項が一つあります。既に同じ名前のファイルが存在している場合は、そのファイルの長さを 0 に切り詰めてからデータを書き込みます。既存のファイルは内容が破壊されることに注意してください。
それでは簡単な例題として、string list の要素を 1 行ずつファイルに書き込む関数 output_stringList を作ってみましょう。次のリストを見てください。
リスト : ファイルの書き込み fun output_stringList(data, filename) = let open TextIO val a = openOut filename in app (fn x => output(a, x ^ "\n")) data; closeOut a end
最初に openOut でファイルをオープンします。あとは、高階関数 app を使って data から要素を一つずつ取り出し、改行文字を付加してから output で出力するだけです。
このほかにも、SML/NJ にはいろいろな入出力関数が用意されています。詳しい説明は SML/NJ ライブラリのマニュアル The Standard ML Basis Library を参照してください。