今回は Prolog のファイル入出力について説明します。Prolog の標準とされている処理系にエジンバラ Prolog があります。エジンバラ Prolog では、入出力用に 2 つのストリーム (stream) が用意されています。
ストリームは「流れ」や「小川」という意味ですが、プログラミング言語の場合は「ファイルとプログラムの間でやりとりされるデータの流れ」という意味で使われています。ストリームはファイルと 1 対 1 に対応していて、ファイルからデータを入力する場合は、ストリームを経由してデータが渡されます。逆に、ファイルへデータを出力するときも、ストリームを経由して行われます。
ここでは、入力ストリームを current input と呼び、出力ストリームを current output と呼ぶことにします。デフォルトでは、current input が標準入力、current output が標準出力に設定されています。Prolog のファイル入出力は、current input と current output の設定をファイルへ切り替えることで行います。次の表を見てください。
述語名 | 機能 |
---|---|
see(File) | ファイル File を入力用にオープンして current input に設定する |
seen | current input に設定されているストリーム(ファイル)をクローズする |
seeing(File) | current input に設定されているストリームを求める |
tell(File) | ファイル File を出力用にオープンして current output に設定する |
told | current output に設定されているストリーム(ファイル)をクローズする |
telling(File) | current output に設定されているストリームを求める |
ファイル名の指定にはアトムを使います。ファイル名にアトムで使用できない文字が含まれている場合には、ファイル名をシングルクオート ( ' ) で囲んでください。たとえば、ピリオド ( . ) を含むファイル名 test.dat は 'test.dat' と指定してください。また、特別に user というファイル名を指定すると標準入出力が対象になります。
ファイルを入力用にオープンするには述語 see(File) を使います。引数 File にはファイル名を指定します。これで current input は File で指定されたファイルになります。あとは、入力用の述語 read を使って、ファイルからデータを読み込むことができます。なお、see でファイルをオープンする場合、ファイルが存在しないとエラーになります。
ファイルを出力用にオープンするには述語 tell(File) を使います。これで current output は File で指定されたファイルになります。あとは、出力用の述語 write や format を使って、データをファイルへ出力することができます。tell でファイルをオープンする場合、ファイルが存在しない場合は新しいファイルを作成します。ファイルが存在する場合は、ファイルを切り詰めてから(ファイルサイズを 0 にしてから)データを書き込みます。既存のファイルは、内容が破壊されることに注意してください。
オープンしたファイルはクローズしないといけません。入力用のファイルは述語 seen で、出力用のファイルは述語 told でクローズします。また、current input と current output に設定されているストリームは述語 seeing(File) と telling(File) で求めることができます。なお、SWI-Prolog の場合、これらの述語でファイル名を求めることはできません。
簡単な使用例を示しましょう。ファイルからデータを読み込んで表示するプログラムです。
リスト:データファイルの表示 write_data(end_of_file). write_data(X) :- format('~a.~n', X). read_data(X) :- see(X), repeat, read(Y), write_data(Y), Y == end_of_file, !, seen.
述語 read_data(X) は、引数 X で指定されたファイルからデータを読み込んで表示します。データの入力には述語 read を用いるので、データはピリオド (.) で区切ってください。たとえば、test.dat には次のようなデータが格納されているとします。
prolog. programming. siliconvalley. oakland.
最初にファイルを see でオープンします。これで述語 read を使って、ファイルからデータを読み込むことができます。あとは、ファイルの終了 (end_of_file) になるまで、ファイルからデータを読み込んで write_data で出力します。repeat と Y == end_of_file で、失敗駆動ループを構成していることに注意してください。write_data は format でデータを出力するだけですが、最初の節でデータが end_of_file の場合は出力しないようにしています。最後に、seen でファイルをクローズします。
実行例は次のようになります。
?- read_data('test.dat'). prolog. programming. siliconvalley. oakland. Yes
きちんと動作していますね。もしも、ほかのファイルへ出力したい場合は、次のようにプログラムすればいいでしょう。
リスト:データファイルのコピー copy_data(X, Y) :- see(X), tell(Y), repeat, read(Z), write_data(Z), Z == end_of_file, !, seen, told.
copy_data の引数 X が入力ファイルで、Y が出力ファイルを表します。入力ファイルは see でオープンし、出力ファイルは tell でオープンします。これで、write や format の出力はファイル Y へ書き込まれます。ファイルの読み込みと書き込みは read_data と同じです。あとは、seen と told でファイルをクローズするだけです。
それでは実行例を示します。
?- copy_data('test.dat','test1.dat'). Yes ?- read_data('test1.dat'). prolog. programming. siliconvalley. oakland. Yes
test.dat の内容は test1.dat にコピーされています。正常に動作していますね。
入出力用の述語は read や write のほかに、バイト単位で行う get や put もあります。次の表を見てください。
述語名 | 機能 |
---|---|
get(C) | 空白文字以外の 1 文字を入力する |
get0(C) | 1 文字入力する |
put(C) | 1 文字出力する |
これらの述語の引数 C は、文字を表す ASCII CODE (数値) です。get と get0 の場合、ファイルの終了は -1 になります。簡単な例を示しましょう。
?- get(C). |: a C = 97 Yes ?- get(C). |: <- 空白を入力 |: b C = 98 Yes ?- get0(C). |: <- 空白を入力 C = 32 Yes ?- put(97). a Yes
get0 と put を使うと、ファイルの内容を表示するプログラムを作ることができます。次のリストを見てください。
リスト:ファイルの内容を表示 write_code(-1). write_code(C) :- put(C). type_file(X) :- see(X), repeat, get0(C), write_code(C), C == -1, !, seen.
type_file ではファイルから get0 で 1 文字読み込み、write_code では put で 1 文字出力します。ファイルの終了は -1 なので、repeat と C == -1 で失敗駆動ループを構成しています。また、write_code でも最初の節で -1 を出力しないようにしています。これでファイルの内容を表示することができます。
述語 see と tell はファイルをオープンするだけではなく、current input と current output の設定を他のファイルへ切り替えることができます。この機能を使って、複数のファイルを扱うことができます。
SWI-Prolog の場合、see や tell でファイルをオープンすると、そのファイルに対応したストリームが作成され、それが current input や current output に設定されます。このストリームは述語 seeing と telling で求めることができます。そして、see や tell の引数にストリームを与えると、current input や current output の設定をそのストリームに変更することができます。
たとえば、2 つのファイル A, B からデータを読み込む場合を考えてみましょう。最初に、see(A) でファイル A をオープンし、seeing(As) でストリームを As に求めます。同様に、see(B), seeing(Bs) でファイル B をオープンしてストリームを Bs に求めます。
次に、see(As) を実行すると、current input の設定はファイル A のストリームになります。ここで read を実行すると、ファイル A からデータを読み込みます。その次に see(Bs) を実行すると、current input はファイル B のストリームに設定されるので、read を実行するとファイル B からデータを読み込むことができます。
Prolog の場合、see や tell でオープンしたファイルは、seen や told でクローズしない限り継続して使用できるので、current input や current output を切り替えたあとでも、引き続きデータの読み込みや書き込みを行うことができます。
簡単な例題として、2 つのファイルからデータを入力して、1 つのファイルへデータを出力するプログラムを作ってみましょう。次の図を見てください。
入力ファイル test2.dat test3.dat --------- --------- abc. 100. def. 200. ghi. 300. 出力ファイル test4.dat --------- abc. 100. def. 200. ghi. 300.
ファイル test2.dat と test3.dat からデータをひとつずつ読み込み、それらのデータをファイル test4.dat に書き込みます。プログラムは次のようになります。
リスト:データファイルの連結 write_data(end_of_file, _). write_data(X, Y) :- format('~a. ~a.~n', [X, Y]). cat_data(X, Y, Z) :- see(X), seeing(Xs), see(Y), seeing(Ys), tell(Z), repeat, see(Xs), read(Xd), see(Ys), read(Yd), write_data(Xd, Yd), Xd == end_of_file, !, see(Xs), seen, see(Ys), seen, told.
述語 cat_data の引数 X, Y が入力ファイル、Z が出力ファイルを表します。最初に、see と seeing で入力ファイルをオープンして、そのストリームを Xs と Ys に求めます。そして、出力ファイル Z を tell でオープンします。
次に、入力ファイルからデータを読み込みます。see(Xs) で current input をファイル X に切り替えて、データを read(Xd) で 1 つ読み込みます。同様に、see(Ys), read(Yd) でファイル Y からデータを 1 つ読み込みます。このように see で current input の設定を切り替えることで、複数のファイルからデータを入力することができます。
あとは write_data で読み込んだデータをファイル Z へ出力します。最後に、seen と told でファイルをクローズします。なお、このプログラムはファイル終了のチェックをファイル X でしか行っていません。もしも、ファイル X のデータ数がファイル Y よりも多い場合は、ファイル X のデータと end_of_file が書き込まれることになります。ご注意ください。
それでは実行例を示しましょう。次のように cat_data を実行すると、test2.dat と test3.dat のデータが test4.dat に書き込まれます。
?- cat_data('test2.dat','test3.dat','test4.dat'). Yes
実際に試してみてください。
Prolog には、ファイルからプログラムを読み込む述語 consult(File) が用意されています。プログラムの読み込みは、リストの表記法 [File]. でも行うことができます。複数のプログラムをまとめて読み込むときは、リストの表記法を使った方が便利でしょう。
cosult(FileA),cosult(FileB),cosult(FileC). ==> [FileA, FileB, FileC].
標準的な Prolog の場合、プログラムを読み込む述語に consult と reconsult があります。consult は単純にプログラムを追加していくので、同じプログラムが複数定義されることがあります。たとえば、プログラムに foo(a, b). が定義されていて、同じプログラムを再度 consult で読み込むと、foo(a, b) の定義が 2 つに増えるのです。reconsult の場合、重複するプログラムは更新するだけなので、同じ定義が 2 つに増えることはありません。
SWI-Prolog の場合、consult は reconsult のように動作するので注意してください。簡単な例を示しましょう。次のプログラムを読み込みます。
foo(a, b). foo(c, d). foo(e, f).
ファイル名を test5.swi とし、SWI-Prolog でファイルを読み込みます。
?- ['test5.swi']. % test5.swi compiled 0.00 sec, 728 bytes Yes ?- listing(foo). foo(a, b). foo(c, d). foo(e, f). Yes ?- ['test5.swi']. % test5.swi compiled 0.00 sec, 0 bytes Yes ?- listing(foo). foo(a, b). foo(c, d). foo(e, f). Yes
このように、test5.swi を 2 度読み込んでも、同じ定義が 2 つに増えることはありません。一般的な Prolog の場合、consult でプログラムを読み込むと、次のように foo の定義が増えてしまいます。
| ?-listing(foo). foo(a,b). foo(c,d). foo(e,f). foo(a,b). foo(c,d). foo(e,f). yes
このような場合は reconsult でプログラムを読み込んでください。
SWI-Prolog で端末から事実や規則を入力したい場合は、consult や [ ] などプログラムを読み込む述語に user を指定します。user は標準入出力を指定する特別なファイル名です。
簡単な実行例を示します。入力の終了は ^D (Ctrl + d) です。Windows では ^Z (Ctrl + z) でもかまいません。
?- [user]. |: foo(a, b). |: foo(c, d). |: (ここで ^D を入力) % user compiled 14.55 sec, 528 bytes Yes ?- foo(X, Y). X = a Y = b ; X = c Y = d ; No ?-
このように、ファイル名を user に設定すると、端末から事実や規則を入力することができます。