制御構造まとめ
- >と','や;の挙動が全然分かっていなかったので調べてみた。
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 ?-