Prolog:临界区、回溯、错误处理
Prolog: critical section, backtracking, error handling
我正在尝试在 SWI-Prolog 中编写一个由互斥锁保护的关键部分,并且一直在考虑使用 setup_call_cleanup/3
和 setup_call_catcher_cleanup/4
。
我遇到的问题是我的 目标 是一系列操作,其中任何一个都可能失败,这意味着系统回溯到 setup_call_cleanup
的开始并调用清理。不幸的是,通过回溯我无法适当地报告错误。为了说明我的问题,让我们考虑这个简单的例子:
setup_call_cleanup(
mutex_lock(mtx),
( Step1 = true, Step2 = true, Step3 = true ),
( mutex_unlock(mtx), writeln([Step1, Step2, Step3]) ).
并将其与以下内容进行比较:
setup_call_cleanup(
mutex_lock(mtx),
( Step1 = true, Step2 = true, fail, Step3 = true ),
( mutex_unlock(mtx), writeln([Step1, Step2, Step3]) ).
在第一种情况下一切正常 -- 我可以看到所有步骤都已完成。但是在第二种情况下,我看不到 Step1
和 Step2
已经执行。我想看看它,因为它们可能具有回溯无法撤消的外部副作用。此外,我不想在 Goal 中包含错误处理以使关键部分尽可能精简和快速。
我有两个想法:
- 用
nb_setval
装饰每个步骤以存储一个值来指示已完成的步骤,
- 重新编码步骤,使它们抛出带有问题细节的异常。
前者会使代码变得相当臃肿,而后者似乎对我的需求来说过于重量级。有没有类似setup_nb_call_cleanup
的东西?
我认为,诀窍是 运行 一个一个地实现目标,防止错误和失败以及 return 失败的步骤。好的开始是
until_failure((A,B), Result) :-
!,
until_failure(A, Result),
( var(Result)
-> until_failure(B, Result)
; true
).
until_failure(G, Result) :-
( catch(G, Result, true)
*-> true
; Result = false(G)
).
现在您可以运行例如
?- until_failure((Step1 = true,
Step2 = true,
fail,
Step3 = true), Result),
writeln([Step1, Step2, Step3]).
[true, true, _5742]
Result = false(fail)
参见http://swish.swi-prolog.org/p/ReQWsvCg.swinb。 SWISH 不允许
处理互斥量,但您可以轻松地将其包装在 with_mutex/2
中。细节主要取决于您希望如何处理非确定性。
感谢 Jan 的启发;很有用。我最终编写了一个类似的 step_by_step
规则:
step_by_step(Goal, Steps, Error) :-
step_by_step_(Goal, 0, Steps, Error).
step_by_step_((A, B), InStep, OutStep, Error) :-
!,
step_by_step_(A, InStep, OutStep1, Error),
( var(Error) ->
step_by_step_(B, OutStep1, OutStep, Error)
;
OutStep = InStep
).
step_by_step_(Goal, InStep, OutStep, Error) :-
( catch(Goal, Ex, (Error = exception(Ex), OutStep = InStep)) *->
(OutStep is InStep + 1 ; true), !
;
Error = false(Goal),
OutStep = InStep
).
我对 (OutStep is InStep + 1 ; true), !
不满意,但找不到更好的方法。
无论如何,规则给了我想要的:
-- 如果一切正常,它只是按顺序运行所有步骤:
?- step_by_step((Step1 = true, Step2 = true, Step3 = true), Steps, Error).
Step1 = Step2, Step2 = Step3, Step3 = true,
Steps = 3.
--如果其中一个步骤失败或抛出异常,则returns成功完成的步骤数和失败的目标:
?- step_by_step((Step1 = true, Step2 = true, fail, Step3 = true), Steps, Error).
Step1 = Step2, Step2 = true,
Steps = 2,
Error = false(fail).
或异常:
?- step_by_step((Step1 = true, Step2 = true, throw(bomb), Step3 = true), Steps, Error).
Step1 = Step2, Step2 = true,
Steps = 2,
Error = exception(bomb).
我正在尝试在 SWI-Prolog 中编写一个由互斥锁保护的关键部分,并且一直在考虑使用 setup_call_cleanup/3
和 setup_call_catcher_cleanup/4
。
我遇到的问题是我的 目标 是一系列操作,其中任何一个都可能失败,这意味着系统回溯到 setup_call_cleanup
的开始并调用清理。不幸的是,通过回溯我无法适当地报告错误。为了说明我的问题,让我们考虑这个简单的例子:
setup_call_cleanup(
mutex_lock(mtx),
( Step1 = true, Step2 = true, Step3 = true ),
( mutex_unlock(mtx), writeln([Step1, Step2, Step3]) ).
并将其与以下内容进行比较:
setup_call_cleanup(
mutex_lock(mtx),
( Step1 = true, Step2 = true, fail, Step3 = true ),
( mutex_unlock(mtx), writeln([Step1, Step2, Step3]) ).
在第一种情况下一切正常 -- 我可以看到所有步骤都已完成。但是在第二种情况下,我看不到 Step1
和 Step2
已经执行。我想看看它,因为它们可能具有回溯无法撤消的外部副作用。此外,我不想在 Goal 中包含错误处理以使关键部分尽可能精简和快速。
我有两个想法:
- 用
nb_setval
装饰每个步骤以存储一个值来指示已完成的步骤, - 重新编码步骤,使它们抛出带有问题细节的异常。
前者会使代码变得相当臃肿,而后者似乎对我的需求来说过于重量级。有没有类似setup_nb_call_cleanup
的东西?
我认为,诀窍是 运行 一个一个地实现目标,防止错误和失败以及 return 失败的步骤。好的开始是
until_failure((A,B), Result) :-
!,
until_failure(A, Result),
( var(Result)
-> until_failure(B, Result)
; true
).
until_failure(G, Result) :-
( catch(G, Result, true)
*-> true
; Result = false(G)
).
现在您可以运行例如
?- until_failure((Step1 = true,
Step2 = true,
fail,
Step3 = true), Result),
writeln([Step1, Step2, Step3]).
[true, true, _5742]
Result = false(fail)
参见http://swish.swi-prolog.org/p/ReQWsvCg.swinb。 SWISH 不允许
处理互斥量,但您可以轻松地将其包装在 with_mutex/2
中。细节主要取决于您希望如何处理非确定性。
感谢 Jan 的启发;很有用。我最终编写了一个类似的 step_by_step
规则:
step_by_step(Goal, Steps, Error) :-
step_by_step_(Goal, 0, Steps, Error).
step_by_step_((A, B), InStep, OutStep, Error) :-
!,
step_by_step_(A, InStep, OutStep1, Error),
( var(Error) ->
step_by_step_(B, OutStep1, OutStep, Error)
;
OutStep = InStep
).
step_by_step_(Goal, InStep, OutStep, Error) :-
( catch(Goal, Ex, (Error = exception(Ex), OutStep = InStep)) *->
(OutStep is InStep + 1 ; true), !
;
Error = false(Goal),
OutStep = InStep
).
我对 (OutStep is InStep + 1 ; true), !
不满意,但找不到更好的方法。
无论如何,规则给了我想要的:
-- 如果一切正常,它只是按顺序运行所有步骤:
?- step_by_step((Step1 = true, Step2 = true, Step3 = true), Steps, Error).
Step1 = Step2, Step2 = Step3, Step3 = true,
Steps = 3.
--如果其中一个步骤失败或抛出异常,则returns成功完成的步骤数和失败的目标:
?- step_by_step((Step1 = true, Step2 = true, fail, Step3 = true), Steps, Error).
Step1 = Step2, Step2 = true,
Steps = 2,
Error = false(fail).
或异常:
?- step_by_step((Step1 = true, Step2 = true, throw(bomb), Step3 = true), Steps, Error).
Step1 = Step2, Step2 = true,
Steps = 2,
Error = exception(bomb).