Prolog 写経記 その 65 read_line/1

(ほぼ) 毎日淡々と Prolog を写経します.元ネタはこちら.

Prologユーティリティライブラリ

Prologユーティリティライブラリ

今日からは「第 6 章 入力ユーティリティ」へ進みます.
まずは read_line/1 を写経します.

解説

read_line(X) は現在の入力ストリームにおいて,行の最後までに読み込まれた文字のすべてからなるアトム X を返す.

ふむ.Java でいうと BufferedReader#readLine() みたいな.

モード

read_line(-).

ふむ.

定義

では,こいつの定義を写経しませう.

read_line(X) :-
	get0(Char),
	get0_chars(Char, ASCII),
	name(X, ASCII), !.

get0_chars(26, []) :- !,
	seeing(File),
	assert(eof(File)).
get0_chars(10, []) :- !.
get0_chars(Char, [Char | ASCII]) :-
	get0(Char1), !,
	get0_chars(Char1, ASCII).

うーみゅ...
まずは本体,read_line/1 ですが,ここではまず get0/1 で一文字を読み込みます.
そして get0_chars/2 で行の残りを読み込み,その結果を ASCII として受け取ります.
それを name/2 でアトムにして返します.


下請けの述語 get0_chars/2 は 3 つの節を持っています.
最初の節は読み込んだ文字が EOF だった場合にマッチするそうです.26... 16 進だと 1A.今でも有効なんだろうか? ともあれ (JW),その場合は seeing/1 で現在読み込んでいるファイルを取得し,それが eof/1 にマッチするように Prolog データベースに登録しています.うーみゅ...
2 番目の節は読み込んだ文字が行末だった場合にマッチします.10... 16 進だと 0A.いわゆる '\n'.その場合は空リストを返します.
最後の節は通常の文字を読み込んだ場合にマッチします.その場合はさらにもう一文字を読み込んで get0_chars/2再帰的に呼び出し,その結果の先頭に第 1 引数で渡された文字を連結したものが結果となります.


なんというか,いろいろな意味で微妙.
一番微妙なのは EOF に達したことを示すのに Prolog データベースに述語 (っていうか節) を登録していることですね.
さらに,ここで使われている get0/1seeing などは ISO 標準には含まれていないようで,代わりに get_char/1 などが用意されているようです.


そんなわけで (どんなわけで?),ざっくりと書き換えちゃいましょう.
え? それじゃ写経じゃない? いいのいいの,ただ単に写すことが目的じゃないんだから.
まずは EOF の判定ですが,BufferedReader#readLine() なんかだと結果を null で返すことができるわけですが,Prolog の項っていうか,この場合はアトムですが,null 相当のものってあるのでしょうか?
一つ考えられるのは X を具体化しないでおくということですが,それはそれで扱いにくいかも.
そこで,引数を一つ増やしちゃいましょう.つまり,

read_line2(X, EOF)

を作ります.
試行錯誤した結果,こうなりました.

read_line2(X, EOF) :-
	get_char(Char),
	read_rest(Char, ASCII, EOF),
	name(X, ASCII), !.

read_rest(end_of_file, [], true) :- !.
read_rest('\n', [], false) :- !.
read_rest(Char, [Char | ASCII], EOF) :-
	get_char(Char1), !,
	read_rest(Char1, ASCII, EOF).

下請けの述語,get0_chars はどうも好きになれないので read_rest に変更.行の残りを読むという気持ちを表現してみました.
ISO 標準の get_char は入力ストリームが EOF に達すると end_of_file というアトムを返すそうな.
その他はあまり変わっていません.

注記

入力行の最後にはピリオドは必要でない.も指呼の述語が正しく動作しない場合は,述語 ge0_chars/2 の 2 番目の節中の 10 を 13 に代えてみよ.

ふむ.
10 を 13 へというのは LF を CR にしてみろということね.らじゃあ.

では使用例を写経しませう.

filecopy(InputFile, OutputFile) :-
	see(InputFile),
	tell(OutputFile),
	repeat,
	read_line(L),
	write(L), nl,
	eof(InputFile),
	told,
	seen.

またまたテスト用の述語が登場.
なのですが,ここでも ISO 標準には含まれていない述語がふんだんに使われています.ちなみに see/1 は入力ファイルのオープン,tell/1 は出力ファイルのオープン,told/1 は出力ファイルのクローズ,seen/1 は入力ファイルのクローズ.わかんないよ.
さらに,EOF の判定に Prolog データベースに eof/1 が登録されることに依存しているので,これも自前の read_line2/2 を使うように変更したバージョンを用意しませう.

filecopy2(InputFile, OutputFile) :-
	current_input(Stdin),
	open(InputFile, read, In),
	set_input(In),
	current_output(Stdout),
	open(OutputFile, write, Out),
	set_output(Out),

	repeat,
	read_line2(L, EOF),
	write(L), nl,
	EOF = true,

	close(In),
	set_input(Stdin),
	close(Out),
	set_output(Stdout).

ちょっとくどいかなぁ.
ともあれ (JW),ISO 標準の組み込み述語は他言語になれた身にはわかりやすい名前ですね.


さっそく使ってみます.

40 ?- filecopy2('foo.txt', 'bar.txt').

Yes

できあがったファイルをチェックしたところ,見事にコピーされていました.\(^o^)/
細かいことをいうと,最後に改行が一つ余計に付いちゃうんですけどね.まぁいいや.