【问题标题】:Implementing often-occuring determinism patterns in Prolog在 Prolog 中实现经常出现的确定性模式
【发布时间】:2014-08-03 21:47:04
【问题描述】:

在 Prolog 中编程时,我经常编写谓词,当调用时所有参数都实例化时,其行为应该是半确定性的(否则其行为应该是非确定性的)。

一个具体的用例是我的谓词walk/3,它实现了图形遍历。由于两个顶点之间可以存在多个路径,因此实例化(+,+)true 之后给出了多个选择点。然而,这些都是毫无用处的。出于性能原因,调用代码必须显式使用once/1

%! walk(+Graph:ugraph, +StartVertex, +EndVertex) is semidet.
%! walk(+Graph:ugraph, -StartVertex, +EndVertex) is nondet.
%! walk(+Graph:ugraph, +StartVertex, -EndVertex) is nondet.
%! walk(+Graph:ugraph, -StartVertex, -EndVertex) is nondet.

可以通过在调用上下文中使用 once/1 来强制执行半确定性,但我想将半确定性实现为谓词 walk/3 的属性,而不是作为必须特别对待每个调用它的时间。

除了对代码美学的关注之外,调用上下文不需要总是知道它对walk/3 的调用是否是半确定性的。例如:

%! cycle(+Graph:ugraph, +Vertex) is semidet.
%! cycle(+Graph:ugraph, -Vertex) is nondet.

cycle(Graph, Vertex):-
  walk(Graph, Vertex, Vertex).

我想出了以下解决方案,它确实产生了正确的行为。

walk_wrapper(Graph, Start, End):-
  call_ground_as_semidet(walk(Graph, Start, End)).

:- meta_predicate(call_ground_as_semidet(0)).
call_ground_as_semidet(Goal):-
  ground(Goal), !,
  Goal, !.
call_ground_as_semidet(Goal):-
  Goal.

但是,这种解决方案存在不足:

  • 这还不够通用,例如有时ground 应该是nonvar
  • 它不是风格,每次使用时都需要一个额外的谓词包装器。
  • 也可能有点效率低下

我的问题是:是否还有其他方法可以在 Prolog 中对经常出现的(非)确定性模式(如此处描述的模式)进行通用/高效/风格化编程?

【问题讨论】:

  • 只是为了确保我理解你的问题,举一个更简单的例子:你想要一个谓词,当第一个参数为地面时,行为为memberchk/2,当第一个参数不是时,行为为member/2地面?
  • 你检查过mavis pack 吗?
  • @Boris: library(xpath) 对于效率和控制产生影响的应用环境来说,它是一个很好的候选者
  • memberchk/2member/2 都不是 ISO。但至少,member/2 是 Prolog 序言的一部分。
  • @CapelliC: memberchk/2 没有声明性意义:memberchk(a,Xs),Xs=[b,a]. 失败但memberchk(a,Xs),Xs=[b,a]. 成功。

标签: coding-style prolog deterministic


【解决方案1】:

您应该尝试双重否定失败。是的,地面目标只能是真或假,所以它不应该留下任何选择点。让我们假设我们有一个无环图,为简单起见:

如果我使用此代码:

edge(a, b).         edge(a, c).
edge(a, d).         edge(b, c).
edge(c, d).         edge(c, e).
edge(d, e).

path(X,X).
path(X,Y) :- edge(X,Z), path(Z,Y).

Prolog 系统现在将为关闭的查询留下选择点:

?- path(a, e).
true ;
true ;
true ;
true ;
true ;
false.

在我看来,推荐的方法是消除这些 选择点,但有一个多模态谓词, 就是在 Prolog 中使用所谓的元编程。

元编程有时也被贬义地称为 非逻辑编程,因为它基于非逻辑 谓词,例如 ground/1、!/0 或 (+)/1。但是让我们打电话 它在声明性不受影响时进行元编程。

你可以写一个包装 smart/1 如下,做 与您的 call_ground_as_semidet/1 相同,但有细微差别:

smart(G) :- ground(G), !, \+ \+ G.
smart(G) :- G.

Prolog 系统将不再为封闭查询留下选择点:

?- smart(path(a,e)).
true.

\+ \+ 超过一次的优点是前者确实 不仅没有选择点,而且还删除了踪迹。它 有时被称为 Prolog 的垃圾收集元谓词

【讨论】:

    【解决方案2】:

    不是答案,但评论太长了。请记住,我不确定我是否完全理解,所以我想先重新陈述您的问题。

    以您的图表为例。您希望能够使用相同谓词的相同调用提出以下问题。

    给定一个图表,

    问题 1:顶点 B 是否可以从顶点 A 到达(不知何故)? - 是或否

    问题 2:从 A 可以到达哪些顶点? - 回溯枚举

    问题 3:B 可以从哪些顶点到达? - 回溯枚举

    问题 4:哪些 A 和 B 存在,哪些 B 可以从 A 到达? - 回溯枚举

    我在这里可能错了,但似乎回答问题 1 和问题 2 可能会采用与回答问题 3 不同的搜索策略?

    更一般地说,你想有一种说法:如果我有一个是或否的问题,成功还是失败。否则,请列举答案。

    我的麻烦来了:你打算如何处理这两种不同的类型答案?以及哪些情况下您事先不知道您需要哪种类型的答案? (如果您事先知道,可以使用once(goal),正如您自己所说的。)

    PS: 显然有setof/3,如果没有答案就会失败,或者收集所有答案。是否存在您想知道一些答案但又不想收集所有答案的情况?由于答案的大小和数量,这是一个效率问题吗?

    【讨论】:

    • @Boris 您对我的问题的重新表述是正确的。在我给出的具体示例中,Q2 和 Q3 恰好使用相同的搜索策略。想要强制执行半确定性的两个主要原因确实是性能和消除多余的选择点。调用上下文确实可以使用once/1setof/3,但我正在询问是否有解决方案可以应用于目标本身,而不是(全部)其调用上下文?
    • 枚举使程序变得简单lazy Prolog。我非常喜欢——最令人愉快的 Prolog 代码是基于对懒惰的恰当表达。能够高效,这很重要。
    • @WouterBeek 我也许明白了。请原谅循环推理,但如果调用上下文不知道要问的是哪个问题,你怎么知道如何处理答案?换句话说,在调用者链的某个时刻,您必须知道它是半确定性调用还是非确定性调用,对吗? (不太确定....)
    • @Boris 作为调用上下文的示例,它事先不知道要问哪个问题,我可以将 cycle 定义为封闭式步行:cycle(G, V):- walk(G, V, V)。 Cycle 有模式(+,+) semidet(+,-) nondet
    【解决方案3】:

    不是答案,而是建议。 也许我误解了你的问题。我认为您正试图通过强制谓词具有不确定性来解决性能问题。这个问题毫无意义:如果p(X) 是不确定的(多个解决方案),那么p(X),! 是确定的(仅限第一个解决方案)。

    您不应通过更改程序逻辑或谓词可逆性来解决性能问题。我建议一种不同的方法:

    首先,利用 prolog 索引。例如:

    cycle(+Graph:ugraph, +Vertex)
    

    不一样(在性能方面):

    cycle(+Vertex, +Graph:ugraph)
    

    您应该在 Web 上找到有关 prolog 索引(和性能影响)的文档。

    其次,为同一个问题编写多个实现。每个都将针对不同的情况优化性能。然后,编写一个谓词,为每种情况选择最佳实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-01
      • 2015-10-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多