制御構造まとめ

  • >と','や;の挙動が全然分かっていなかったので調べてみた。

true, fail, !

述語 用例 動作
true/0 true 必ず成功する
fail/0 fail 必ず失敗する(強制的にバックトレースを行う)
!/0(カット) ! 必ず成功する。副作用としてカット以前にバックトレースできなくする
true_fail(N) :- true, fail, N = noreached.
true_fail(N) :- N = success.

true_fail_cut(N) :- true, !, fail, N = noreached.
true_fail_cut(N) :- N = success.

?- true_fail(N).                % N = success. yes
?- true_fail_cut(N). % no

',', ;, ->

述語 用例 動作
(',')/2 Goal1 , Goal2 Goal1を実行に成功した場合、Goal2を実行する
(;)/2 Goal1 ; Goal2 最初にバックトレースする点(choice-point)を作りGoal1を実行する。Goal1が失敗した場合、Goal1以前をバックトレースする代わりにGoal2を実行する。
(->)/2 Goal1 -> Goal2 最初にGoal1を実行する。成功した場合、バックトレースでGoal1以前に戻れなくしてからGoal2を実行する。これはif-thenに似ている。Goal1が失敗すると->/2も失敗する。
'test,'(N) :- true, repeat, write(n), fail.

'test->'(N) :- true, repeat -> write(n), fail.

?- 'test,'(N) % nの出力が永遠に続く
?- 'test,'(N) % no

if-then-else

  • >/2は;/2と使われる事が多く、if-then-elseのように利用できる。
Goal1 -> Goal2 ; Goal3.

この時、;の第一引数が"Goal1 -> Goal2"で、"Goal3"が第二引数となる。;は第一引数が失敗したときはバックトレースとして第二引数を実行するようになっている。初めにGoail1を実行し、成功した場合は->によってバックトレースが行えなくしてGoal2を実行する。Goal3が実行されないのは、Goal3がバックトレースだからである。(->によってバックトレースは取り除かれる。)

雰囲気
    ここまで実行が終わった。バックトレースはhoge()
       |
       v
hoge(), Goal1 -> Goal2 ; Goal3.


(Goal1 -> Goal2) ; Goal3.

;はバックトレースを置き換える。バックトレースは Goal3
|
v
;((Goal1 -> Goal2),Goal3).

Goal1 -> Goal2

 Goal1が成功した。->はバックトレースを取り除く。バックトレースはなくなる。
        |
        v
Goal1 -> Goal2


Goal2を実行。
|
v
Goal2


で、
Goal2 が 成功した => yes
         失敗した => バックトレースしたくても、->によってバックトレースは
                     破棄されてしまった。Goal3もhoge()にも戻れない。
                     no

call

述語 用例 動作
call/1 call(Goal) Goalを実行する。Goalが表すゴールに成功した場合、callは成功する。Goalのサブゴールとして!がある場合、callの外側には影響しない。
?- call(true). % yes
?- call(fail). % no

cut_out_call :- !, fail.
cut_out_call.

cut_in_call :- call(!),fail.
cut_in_call.

?- cut_out_call. % no
?- cut_in_call.  % yes

catch, throw

述語 用例 動作
catch/3 catch(Goal, Catcher, Recovery) call(Goal)に似たように利用する。もし成功や失敗ならcatchは成功や失敗である。しかし、Goalの実行中に例外が発生した場合、つまりthrow(Ball)された場合は、制御フローに割り込みが掛かり、catchの呼び出しから戻される。
throw/1 throw(Ball) throw(Ball)は通常の制御フローを変更し、外にあるcatchに制御を移す。throw(Ball)が呼び出された場合、Ballはコピーされ、スタックをcatchを呼び出した所までで戻す。そして、コピーしたBallとcatchのCatcherを単一化する。もしBallとCatcherの単一化が成功したなら、Recoveryをcallを使って実行する。このRecoveryがcatchの成功、失敗を決定する。単一化に失敗したなら、スタックをcatchを呼び出す前まで戻す。そしてBallの例外を投げる。

例外の発生には二つの理由がある。

  • 内部的な理由。組み込み述語が発生させる場合。これは引数に間違ったものを与えた場合(0で割るなど)に発生する。
  • 外部的な理由。何らかの理由でプログラムがthrowを呼び出し処理を中断する場合。
?- catch(A is 1/0, error(R,_),(write(R), nl)).
evaluation_error(zero_divisor)

A = _G173
R = evaluation_error(zero_divisor)

Yes
?-
?- catch(throw(error(a,b)), error(R,_),(write(R), nl)).
a

R = a

Yes
?-