【问题标题】:Prolog: critical section, backtracking, error handlingProlog:临界区、回溯、错误处理
【发布时间】:2017-04-13 11:15:28
【问题描述】:

我正在尝试在 SWI-Prolog 中编写一个由互斥锁保护的关键部分,并且一直在研究使用 setup_call_cleanup/3setup_call_catcher_cleanup/4

我遇到的问题是我的 Goal 是一系列操作,其中任何一个都可能失败,这意味着系统会回溯到 setup_call_cleanup 的开头并调用 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]) ).

在第一种情况下,一切正常——我可以看到所有步骤都已完成。但在第二种情况下,我看不到 Step1Step2 已经执行。我想看看它,因为它们可能具有回溯无法撤消的外部副作用。另外,我不想在 Goal 中包含错误处理,以使关键部分尽可能精简和快速。

我有两个想法:

  1. nb_setval装饰每个步骤以存储一个值来指示已完成的步骤,
  2. 重新编码步骤,以便它们抛出包含问题详细信息的异常。

前者会使代码相当臃肿,而后者对于我的需求来说似乎太重了。有setup_nb_call_cleanup之类的吗?

【问题讨论】:

  • 第二种方法肯定已经比第一种方法好很多了!在处理异常时,还要考虑嵌套调用会发生什么。诸如全局更新之类的不纯谓词使这成为一场噩梦,甚至不可能做到正确。
  • @mat:嗯,是的,不是的。我认为,如果某些步骤像assert(some_fact) 一样简单,那么用 throw 子句包装这些步骤可能会使它变得比需要的更复杂。此外,与nb_setval 相比,我不确定throwcatch 的成本...我真的希望关键部分快。
  • 请使用time/1statistics/2 来测试这两种方法的性能。这将确保您使用最快的版本。很多时候,不纯的解决方案也是最慢的。
  • 方法 (2) 的另一个问题是,有时当它只是失败时,很难将其视为异常。例如,some_fact 不成立,在这种情况下也不例外。我真的很想看到类似nb_catchnb_setup_call_cleanup...

标签: prolog swi-prolog


【解决方案1】:

我认为,诀窍是一个接一个地运行目标,注意错误和失败并返回失败的步骤。一个好的开始是

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 中。具体细节主要取决于您希望如何处理不确定性。

【讨论】:

    【解决方案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
        ).
    

    我对@9​​87654323@ 不满意,但找不到更好的方法。

    不管怎样,规则给了我想要的东西:

    -- 如果一切顺利,它只是按顺序运行所有步骤:

    ?- step_by_step((Step1 = true, Step2 = true, Step3 = true), Steps, Error).
    Step1 = Step2, Step2 = Step3, Step3 = true,
    Steps = 3.
    

    --如果其中一个步骤失败或抛出异常,则返回成功完成的步骤数和失败的目标:

    ?- 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).
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-07
      • 1970-01-01
      • 1970-01-01
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多