Common Lisp允许通过conditions and restarts进行异常处理。粗略地说,当函数抛出异常时,“捕手”可以决定“投掷者”应该如何进行。 Prolog是否提供类似的系统?如果没有,是否可以在现有谓词的基础上构建一个步行和检查调用堆栈?
答案 0 :(得分:5)
ISO/IEC standard of Prolog只提供了一个非常基本的异常和错误处理机制 - 或多或少 - 与Java提供的相当,远离Common Lisp的丰富机制,但仍有一些值得注意的地方。特别是,除了实际的信令和处理机制之外,许多系统提供了类似于unwind-protect
的机制。也就是说,即使存在未处理的信号,也能确保执行目标的方法。
使用throw(Term)
引发/抛出异常。首先使用Term
创建copy_term/2
的副本,然后调用它Termcopy
,然后使用此新副本搜索相应的catch(Goal, Pattern, Handler)
,其第二个参数与{{1}结合}}。执行Termcopy
后,Handler
导致的所有统一都将撤消。因此Goal
无法在执行Handler
时访问存在的替换。并且没有办法继续执行throw/1
的地方。
通过执行throw/1
来表示内置谓词的错误,其中throw(error(Error_term, Imp_def))
对应于ISO's error classes之一,而Error_term
可以提供实现定义的额外信息(如源文件,行号等。)
在许多情况下,在本地处理错误会带来很大好处,但许多实现者认为它太复杂而无法实现。
使Prolog处理器在本地处理每个错误的额外工作是非常可观的,并且比Common Lisp或其他编程语言大得多。这是由于Prolog统一的本质。本地处理错误需要撤消在内置执行期间执行的统一:实现者因此有两种可能性来实现:
在内置函数中利用WAM寄存器会导致类似的复杂性。同样,可以在慢速系统或具有显着实现开销的系统之间进行选择。
然而,许多系统提供了内部更好的机制,但很少有系统为程序员提供这些机制。 IF / Prolog提供的Imp_def
与exception_handler/3
具有相同的参数,但在本地处理错误或异常:
[user] ?- catch((arg(a,f(1),_); Z=ok), error(type_error(_,_),_), fail). no [user] ?- exception_handler((arg(a,f(1),_); Z=ok), error(type_error(_,_),_), fail). Z = ok yes
这个内置由不少系统提供。它与catch/3
非常相似,但由于Prolog的回溯机制,需要一些额外的复杂性。见其current definition。
所有这些机制都需要由系统实现者提供,它们不能建立在ISO Prolog之上。
答案 1 :(得分:1)
ISO prolog定义了这些谓词:
throw/1
会抛出异常。参数是抛出的异常(任何术语)catch/3
执行目标并捕获某些异常,在这种情况下它会执行异常处理程序。第一个参数是要调用的目标,第二个参数是异常模板(如果throw/1
抛出的异常与此模板统一,则执行处理程序目标),第三个参数是执行处理程序目标。使用示例:
test:-
catch(my_goal, my_exception(Args), (write(exception(Args)), nl)).
my_goal:-
throw(my_exception(test)).
关于你的笔记“如果没有,可以在现有谓词之上构建一个用于行走和检查调用堆栈吗?”我认为没有一般方法可以做到这一点。也许看看你正在使用的prolog系统的文档,看看是否有某种方式来遍历堆栈。
答案 2 :(得分:1)
正如他在回答中提到的那样,ISO Prolog不允许这样做。然而,一些实验表明,SWI-Prolog提供了一种可以建立条件和重启的机制。接下来是一个非常粗略的概念证明。
“catcher”调用restart/2
来调用目标并提供谓词,以便在出现条件时选择可用的重新启动。 “投掷者”调用signal_condition/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
).
答案 3 :(得分:1)
您可以使用假设推理来实现您想要的。让我们说 允许假设推理的Prolog系统支持以下内容 推理规则:
G, A |- B
----------- (Right ->)
G |- A -> B
有一些Prolog系统支持此功能,例如lambda Prolog。 您现在可以使用假设推理来实现例如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).
解决方案不会遍及整个堆栈跟踪,但是 直接查询处理程序。但它引出了我是否需要的问题 一个特殊的Prolog或我是否可以自己做假设的推理。 作为第一近似,( - :)/ 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。但除此之外,解决方案很可能比推杆慢 通过调用堆栈跟踪的目标。但人们可以想象进一步的改进 一个Prolog系统,例如( - :)/ 2作为原始和适当的编译 技术。
再见