追加Prolog的附加错误是什么?

时间:2017-03-21 16:40:02

标签: prolog append

根据我大学的逻辑课程,我们可以期待与Prolog定义的结果不同的结果:

append([], a, X)

(统一为X=a)。

但是我没有得到他们的目标?考虑到追加应该统一X(在本例中)是[]a的串联,应该期望什么作为有效响应?

我认为他们可能期望返回false[a];但是我想这应该是连接a[]而不是[]a的结果(因为[][a]的尾部)。

3 个答案:

答案 0 :(得分:7)

这里的要点是,我们希望append/3仅为列表保留

在您显示的查询中,a 列表,但append/3 仍然成立。

因此,这种关系实际上比我们最初预期的更通用:它也适用于其他情况!

之所以如此,可以很快从append/3的传统定义的第一个条款中解释:

append([], Bs, Bs).

仅此条款已使查询成功!没有额外的纯子句可以阻止这一点。因此,如果我们希望关系只保留列表的 ,则此子句必须限制。这意味着,我们必须在第二个参数上放置约束,我们通过在子句的主体中声明它来做:

append([], Bs, Bs) :- ... (left as an exercise)

这显然是有代价的:性能。

所以,这里的权衡取决于性能和精度。在Prolog中,我们经常接受这样的权衡,因为我们隐含地将这样的谓词与预期的术语一起使用。另一方面,对于许多谓词,如果未使用预期类型调用它们,我们希望从域错误类型错误中受益。

答案 1 :(得分:5)

您的课程目标是Prolog编程的一个非常重要的方面。

append/3和类似谓词的精确定义上,手册通常非常草率。事实上,完整的定义非常复杂,通常最好只定义实际关系的一部分。考虑Prolog prologue中的第一个定义:

  如果append(Xs, Ys, Zs)是列表ZsXs的串联,则

Ys为真。

注意if。因此,该定义给出了案例,其中关系成立,但没有明确排除其他案例。要排除其他情况,它会说iff。提到的案例(我们正在讨论列表)是谓词的预期用途。那么现在可能还包括哪些案例呢?那些前提条件(参数是列表)不成立的情况。

考虑append/3的定义&f; iff'取代'如果':

append([], Xs, Xs) :-
   list(Xs).
append([X|Xs], Ys, [X|Zs]) :-
   append(Xs, Ys, Zs).

list([]).
list([X|Xs]) :-
   list(Xs).

现在添加两个列表的费用是Xs | + | Ys |。与| Xs |相比,这是一个相当大的开销单独

但情况更糟。考虑一下查询:

?- append([1,2], Ys, Zs).
;  Ys = [], Zs = [1,2]
;  Ys = [_A], Zs = [1,2,_A]
;  Ys = [_A,_B], Zs = [1,2,_A,_B]
...

所以我们得到了这个查询的无限多个答案。将其与通常的定义进行对比:

?- append([1,2], Ys, Zs).
   Zs = [1,2|Ys].

只有单一答案!它包含所有列表的所有答案以及您观察到的一些奇怪案例。因此,append的通常定义具有更好的终止属性。实际上,如果第一个或第三个参数是已知长度 1 的列表,它就会终止。

请注意,答案包含Ys。以这种方式,无数多个答案可以折叠成一个答案。这实际上是逻辑变量的力量!我们可以用有限的手段来表示无数的解决方案。支付的价格是一些额外的解决方案 2 ,可能会导致编程错误。因此需要采取一些预防措施。

1它还会在append([a|_],_,[b|_])等其他一些模糊的案例中终止。

2 append([a], Zs, Zs).也会(在许多系统中)产生答案。

答案 2 :(得分:3)

  

但是我没有得到他们的目标?

如果不问他们,当然不可能知道他们的目标是什么。

尽管如此,我认为他们的目的是表明 Prolog (或多或少) 无类型 append/3记录为:

  

<强> append(?List1, ?List2, ?List1AndList2)

     

List1AndList2List1List2的串联。

很明显,人们希望这三个参数是列表,a不是列表。 a不是[]a的串联,因为人们会认为这两者不是“ concatenatable ”。

现在这仍然成功,因为append/3通常实现为:

append([],T,T).
append([H|T],T2,[H|R]) :-
    append(T,T2,R).

因此,如果您给它append([],a,X).,它将简单地与第一个子句统一并统一X = a

append([14],a,X)发生同样“怪异”的行为。这里X = [14|a]也不是列表。这是因为Prolog解释器不“知道”它正在使用列表。对于Prolog [A|B]和其他任何仿函数一样。

更多“类型安全”处理此问题的方法可能是:

append([],[],[]).
append([H|T],T2,[H|R]) :-
    append(T,T2,R).
append([],[H|T],[H|R]) :-
    append([],T,R).

或更优雅:

list([]).
list([_|T]) :-
    list(T).

append([],T,T) :-
    list(T).
append([H|T],T2,[H|R]) :-
    append(T,T2,R).

因为在这里我们检查第二个参数是否是一个列表。然而,缺点是,现在我们将在 O(m + n)append/3 m 第一个列表的长度和 n 第二个列表的长度,而在原始代码中,它只需要 O(m)时间。此外请注意,Prolog将在解析时提出警告/错误。在您查询这些内容时,它只会在[]附加a

如果程序在将其提供给解释器时编译/不会引发错误,则不检查类型会导致保证较少。这可能是一件好事,但问题可能是你以一种他们不期望的方式调用某些谓词,这可能最终会引发错误。这就是有时使用静态类型语言的原因:它们“保证”(至少在某种程度上)如果你调用问题,就不会发生这样的错误。当然,这并不意味着程序不能在其他事情上出错(或者根本就没有意义)。例如,是静态类型的,并且有一个附加内容:

(++) [] t2 = t2
(++) (h:t) t2 = h:((++) t t2)

定义“或多或少”相同,但Haskell将派生 (++)的类型为(++) :: [a] -> [a] -> [a]。因为它知道每个函数的输入和输出的类型,所以它可以对它执行微积分,因此在编译时,如果你给(++)不同的东西,它会引发错误。一个清单。

这是否是一件好事当然是一个不同的问题:动态类型的编程语言是故意设计的,因为它允许更多的灵活性。