Prolog是否有条件并重启系统,如Common Lisp?

时间:2012-09-27 20:05:12

标签: prolog iso-prolog

Common Lisp允许通过conditions and restarts进行异常处理。粗略地说,当函数抛出异常时,“捕手”可以决定“投掷者”应该如何进行。 Prolog是否提供类似的系统?如果没有,是否可以在现有谓词的基础上构建一个步行和检查调用堆栈?

4 个答案:

答案 0 :(得分:5)

ISO/IEC standard of Prolog只提供了一个非常基本的异常和错误处理机制 - 或多或少 - 与Java提供的相当,远离Common Lisp的丰富机制,但仍有一些值得注意的地方。特别是,除了实际的信令和处理机制之外,许多系统提供了类似于unwind-protect的机制。也就是说,即使存在未处理的信号,也能确保执行目标的方法。

ISO throw / 1,catch / 3

使用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寄存器会导致类似的复杂性。同样,可以在慢速系统或具有显着实现开销的系统之间进行选择。

exception_handler / 3

然而,许多系统提供了内部更好的机制,但很少有系统为程序员提供这些机制。 IF / Prolog提供的Imp_defexception_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

setup_call_cleanup / 3

这个内置由不少系统提供。它与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作为原始和适当的编译 技术。

再见