Common Lispは、条件と再起動による例外処理を可能にします。大まかに言えば、関数が例外をスローすると、「キャッチャー」は「スローワー」をどのように/続行するかを決定できます。Prologは同様のシステムを提供していますか?そうでない場合は、コールスタックを歩いたり調べたりするために、既存の述語の上に構築できますか?
4 に答える
PrologのISO/IEC標準は、Javaが提供するものにほぼ匹敵し、Common Lispの豊富なメカニズムからはほど遠い、非常に基本的な例外およびエラー処理メカニズムのみを提供しますが、注目に値する点がいくつかあります。特に、実際のシグナリングおよび処理メカニズムに加えて、多くのシステムはに類似したメカニズムを提供しunwind-protect
ます。つまり、他の方法で処理されていない信号が存在する場合でも、目標が実行されることを保証する方法です。
ISOスロー/1、キャッチ/3
例外が発生/スローされthrow(Term)
ます。最初にのコピーTerm
が作成され、copy_term/2
それを呼び出します。次に、この新しいコピーを使用して、2番目の引数が。と統合Termcopy
されている対応するものを検索します。が実行されると、によって引き起こされたすべての統合が取り消されます。したがって、が実行されたときに存在する置換にアクセスする方法はありません。そして、処刑された場所で続ける方法はありません。catch(Goal, Pattern, Handler)
Termcopy
Handler
Goal
Handler
throw/1
throw/1
組み込み述語のエラーは、 ISOのエラークラスの1つに対応するthrow(error(Error_term, Imp_def))
場所を実行することによって通知され、実装で定義された追加情報(ソースファイル、行番号など)を提供する場合があります。Error_term
Imp_def
エラーをローカルで処理することが非常に有益である場合が多くありますが、多くの実装者は、実装するには複雑すぎると見なしています。
Prologプロセッサにすべてのエラーをローカルで処理させるための追加の努力はかなりのものであり、CommonLispや他のプログラミング言語よりもはるかに大きいです。これは、Prologの統合の本質によるものです。エラーのローカル処理では、組み込みの実行中に実行された統合を元に戻す必要があります。したがって、実装者には、これを実装するための2つの可能性があります。
- 組み込み述部を呼び出すときに「選択ポイント」を作成します。これにより、この選択ポイントの作成と後続のバインディングの「追跡」の両方で、多くの追加オーバーヘッドが発生します。
- すべての組み込み述語を手動で調べ、ケースバイケースでエラーの処理方法を決定します。これは実行時のオーバーヘッドの点で最も効率的ですが、最もコストがかかり、エラーが発生しやすいアプローチでもあります。
同様の複雑さは、ビルトイン内のWAMレジスタを悪用することによって引き起こされます。繰り返しになりますが、低速のシステムか、実装のオーバーヘッドが大きいシステムのどちらかを選択できます。
exception_handler / 3
ただし、多くのシステムは内部的に優れたメカニズムを提供しますが、プログラマーに一貫して提供するシステムはほとんどありません。IF / Prologはexception_handler/3
、と同じ引数を持つcatch/3
が、エラーまたは例外をローカルで処理するものを提供します。
[ユーザー]?-catch((arg(a、f(1)、_); Z = ok)、error(type_error(_、_)、_)、fail)。 いいえ [ユーザー]?-exception_handler((arg(a、f(1)、_); Z = ok)、error(type_error(_、_)、_)、fail)。 Z=わかりました はい
setup_call_cleanup / 3
このビルトインは、かなりの数のシステムによって提供されます。これは非常に似てunwind-protect
いますが、Prologのバックトラッキングメカニズムのためにいくつかの追加の複雑さが必要です。現在の定義を参照してください。
これらのメカニズムはすべて、システム実装者が提供する必要があり、ISOPrologの上に構築することはできません。
仮説的な推論を使用して、必要なものを実装できます。仮説的な推論を可能にするPrologシステムが次の推論規則をサポートするとします。
G, A |- B
----------- (Right ->)
G |- A -> B
これをサポートするいくつかのPrologシステムがあります。たとえば、lambdaPrologです。これで、架空の推論を使用して、たとえば、restart/2やsignal_condition/3を実装できます。仮説の推論が(-:) / 2を介して行われると仮定すると、次のようになります。
restart(Goal,Handler) :-
(handler(Handler) -: Goal).
signal_condition(Condition, Restart) :-
handler(Handler), call(Handler,Condition,Restart), !.
signal_condition(Condition, _) :-
throw(Condition).
このソリューションは、スタックトレース全体を横断するのではなく、ハンドラーを直接クエリします。しかし、それは私が特別なプロローグを必要とするのか、それとも私が自分で仮説的な推論を行うことができるのかという疑問を投げかけます。最初の近似として、(-:)/2は次のように実装できます。
(Clause -: Goal) :- assume(Clause), Goal, retire(Clause).
assume(Clause) :- asserta(Clause).
assume(Clause) :- once(retact(Clause)).
retire(Clause) :- once(retract(Clause)).
retire(Clause) :- asserta(Clause).
ただし、ゴールがカットまたは例外を発行した場合、上記は正しく機能しません。したがって、たとえばJekejeke Minlog 0.6で利用できるより良いソリューションは、次のようになります。
(Clause -: Goal) :- compile(Clause, Ref), assume_ref(Ref), Goal, retire_ref(Ref).
assume_ref(Ref) :- sys_atomic((recorda(Ref), sys_unbind(erase(Ref)))).
retire_ref(Ref) :- sys_atomic((erase(Ref), sys_unbind(recorda(Ref)))).
sys_unbind / 1述語は、バインディングリストで元に戻る目標をスケジュールします。SICStusのundo/1に対応しています。バインディングリストはカットに対して回復力があります。sys_atomic / 1は、実行中に外部信号が発生した場合でも、たとえばエンドユーザーが中止を発行した場合でも、元に戻す目標が常にスケジュールされていることを保証します。これは、たとえばsetup_call_cleanup/3の最初の引数がどのように処理されるかに対応します。
ここで句参照を使用する利点は、(-:) / 2の後の目標と継続の間でバックトラックが発生した場合でも、句が1回だけコンパイルされることです。ただし、それ以外の場合、ソリューションは、スタックトレースを呼び出すことで目標を設定するよりも遅くなる可能性があります。しかし、Prologシステムのさらなる改良、例えば(-:)/2を原始的で適切なコンパイル技術として想像することができます。
ISOプロローグはこれらの述語を定義します:
throw/1
これは例外をスローします。引数はスローされる例外です(任意の用語)catch/3
ゴールを実行し、特定の例外をキャッチします。その場合、例外ハンドラーを実行します。最初の引数は呼び出される目標、2番目の引数は例外テンプレート(throw/1
このテンプレートとの統合によってスローされた例外の場合はハンドラーの目標が実行されます)、3番目の引数はハンドラーの目標が実行されます。
使用例:
test:-
catch(my_goal, my_exception(Args), (write(exception(Args)), nl)).
my_goal:-
throw(my_exception(test)).
あなたのメモについて「そうでない場合は、コールスタックを歩いて調べるための既存の述語の上に構築できますか?」これを行う一般的な方法はないと思います。たぶん、使用しているプロローグシステムのドキュメントを見て、スタックをウォークスルーする方法があるかどうかを確認してください。
彼の答えで間違って述べられているように、ISOプロローグはこれを許可していません。ただし、いくつかの実験では、SWI-Prologが条件と再起動を構築できるメカニズムを提供していることが示されています。非常に大まかな概念実証が続きます。
「キャッチャー」はrestart/2
、目標を呼び出すために呼び出し、条件が発生した場合に使用可能な再起動から選択するための述語を提供します。「スローワー」はを呼び出しますsignal_condition/2
。最初の引数は、発生する条件です。2番目の引数は、選択した再起動にバインドされます。再起動が選択されていない場合、条件は例外になります。
restart(Goal, _) :- % signal condition finds this predicate in the call stack
call(Goal).
signal_condition(Condition, Restart) :-
prolog_current_frame(Frame),
prolog_frame_attribute(Frame, parent, Parent),
signal_handler(Parent, Condition, Restart).
signal_handler(Frame, Condition, Restart) :-
( prolog_frame_attribute(Frame, goal, restart(_, Handler)),
call(Handler, Condition, Restart)
-> true
; prolog_frame_attribute(Frame, parent, Parent)
-> signal_handler(Parent, Condition, Restart)
; throw(Condition) % reached top of call stack
).