続々 Prolog で FizzBuzz

田辺さんからコメントいただきました.

触発されて、modを使用しない版を書いてみました。いかがでしょうか。

「いかがでしょうか」と言われてもですね,そんなことされたら「剰余を求めるのに 10 秒かかる」という環境を前提に,それでも剰余を使って並行論理言語 GHC/KL1 へと話を展開していたおいらの立場は? (^^;
まぁ,それはともかくとして,提示していただいたコードはちょっと重複感が激しいので少しスッキリさせてみました.

fizzbuzz(End, L) :-
	fz(1, End, 1, 1, L).

fz(N , End , _, _, []) :- N > End.
fz(Na, End, M3a, M5a, [Head|Rest]) :- Na =< End,
	fz(Na, M3a, M5a, Head),
	Nb is Na + 1,
	(M3a =:= 3 -> M3b = 1 ; M3b is M3a + 1),
	(M5a =:= 5 -> M5b = 1 ; M5b is M5a + 1),
	fz(Nb, End, M3b, M5b, Rest).

fz(_, 3, 5, 'FizzBuzz').
fz(_, 3, M5, 'Fizz') :- M5 =\= 5.
fz(_, M3, 5, 'Buzz') :- M3 =\= 3.
fz(N, M3, M5, N) :- M3 =\= 3, M5 =\= 5.

-> 使ったりして少しずっこい気がしないでもないですが気にしない気にしない.
カウンタが 3 つになるのでどうしてもコードは煩雑に感じますね.


ともあれ (JW),せっかくなので FizzBuzz ネタをもう少し続けてみるテスト.
最初にこのネタを書いたときに

本当はもっとストリーム (パイプライン) を活用するようなコードにした方がそれっぽいんだろうけど,それは置いておくテスト.

なんて書いたわけですが,置いておきっぱなしにするのもなんなので,やってみたよ.
まずは 1 から 100 までのリストを作らなきゃいけないわけですが,Prolog ではそういうのが標準の述語としては見当たらない感じ.
そんなわけで (どんなわけで?),自分で作ってみるテスト.

from_to(F, T, [F|Z]) :- F =< T, !,
	F1 is F + 1,
	from_to(F1, T, Z).
from_to(F, T, []) :- F > T, !.

少し趣旨替えしてガードの最後にはカットを置くことにした.
Prologの技芸 (isbn:4320097106)」を読み返して何となく.


後は from_to/2を使うだけ.

fizzbuzz(N, L) :-
	from_to(1, N, L1),
	fz(L1, L).

fz([X|Xs], [Y|Ys]) :- !,
	M3 is X mod 3,
	M5 is X mod 5,
	fz(X, M3, M5, Y),
	fz(Xs, Ys).
fz([], []) :- !.

fz(_, 0, 0, 'FizzBuzz') :- !.
fz(_, 0, M5, 'Fizz') :- M5 =\= 0, !.
fz(_, M3, 0, 'Buzz') :- M3 =\= 0, !.
fz(N, M3, M5, N) :- M3 =\= 0, M5 =\= 0, !.

下請け述語の名前を fz にしたのは田辺さんからのインスパイヤ


んで,これらをまとめて GHC/KL1 にするとこんな感じか.

from_to(F, T, L) :- F =< T |
	L = [F|Z],
	F1 is F + 1,
	from_to(F1, T, Z).
from_to(F, T, L) :- F > T |
	L = [].

fizzbuzz(N, L) :- true |
	from_to(1, N, L1),
	fz(L1, L).

fz([X|Xs], L) :- true |
	L = [Y|Ys],
	M3 is X mod 3,
	M5 is X mod 5,
	fz(X, M3, M5, Y),
	fz(Xs, Ys).
fz([], L) :- true |
	L = [].

fz(_, 0, 0, V) :- true |
	V = 'FizzBuzz'.
fz(_, 0, M5, V) :- M5 =\= 0 |
	V = 'Fizz'.
fz(_, M3, 0, V) :- M3 =\= 0 |
	V = 'Buzz'.
fz(N, M3, M5, V) :- M3 =\= 0, M5 =\= 0 |
	V = N.

例によって見た目は少し冗長になっただけで全然面白くないわけですが,前回までの方法だとゴール単位で極めて粒度の細かい並列実行をしてもらわないと逐次実行との違いが見いだしにくかったのに対し,こちらはもっと粒度の大きな並列実行が可能.
それは述語 fizzbuzz/2 に出てくる

	from_to(1, N, L1),
	fz(L1, L).

です.
これを 1 から N までのリストを順次作成する「プロセス」と,そのリストを入力として別のリストを出力とする「フィルタ」のように動作する「プロセス」と見ることが可能.
2 つのプロセスが共通の変数によって未完成リスト (すなわちストリーム) L1 で接続され,from_to/3 プロセスがリストの先頭から順次値を埋めていき (すなわちストリームに値を「送信」する),fz/2 プロセスがリストの先頭から順次値を読み出して (すなわちストリームから値を「受信」する),加工した結果を別の未完成リスト (すなわちストリーム) L の先頭から順次値を埋めていく (すなわちストリームに値を「送信」する).
まさにパイプ&フィルタ.
ここでいう「プロセス」は CSP (Communicating Sequential Process) のプロセスそのものなわけですよね.
近頃話題の Erlang における軽量プロセスも同じようなものか.
こういう,並行プロセスのネットワークを組み立てるようなプログラミングというのがとても興味深い今日この頃です.