M.Hiroi's Home Page

Prolog Programming

お気楽 Prolog プログラミング入門

[ PrevPage | Prolog | NextPage ]

ファイル入出力

●ファイル入出力の基本

今回は 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 に設定する
seencurrent input に設定されているストリーム(ファイル)をクローズする
seeing(File)current input に設定されているストリームを求める
tell(File)ファイル File を出力用にオープンして current output に設定する
toldcurrent 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 に設定すると、端末から事実や規則を入力することができます。


Copyright (C) 2004 Makoto Hiroi
All rights reserved.

[ PrevPage | Prolog | NextPage ]