根据我大学的逻辑课程,我们可以期待与Prolog定义的结果不同的结果:
append([], a, X)
(统一为X=a
)。
但是我没有得到他们的目标?考虑到追加应该统一X(在本例中)是[]
和a
的串联,应该期望什么作为有效响应?
我认为他们可能期望返回false
或[a]
;但是我想这应该是连接a
和[]
而不是[]
和a
的结果(因为[]
是[a]
的尾部)。
答案 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)
是列表Zs
和Xs
的串联,则
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)
强>
List1AndList2
是List1
和List2
的串联。
很明显,人们希望这三个参数是列表,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
。
如果程序在将其提供给解释器时编译/不会引发错误,则不检查类型会导致保证较少。这可能是一件好事,但问题可能是你以一种他们不期望的方式调用某些谓词,这可能最终会引发错误。这就是有时使用静态类型语言的原因:它们“保证”(至少在某种程度上)如果你调用问题,就不会发生这样的错误。当然,这并不意味着程序不能在其他事情上出错(或者根本就没有意义)。例如,haskell是静态类型的,并且有一个附加内容:
(++) [] t2 = t2
(++) (h:t) t2 = h:((++) t t2)
定义“或多或少”相同,但Haskell将派生 (++)
的类型为(++) :: [a] -> [a] -> [a]
。因为它知道每个函数的输入和输出的类型,所以它可以对它执行微积分,因此在编译时,如果你给(++)
不同的东西,它会引发错误。一个清单。
这是否是一件好事当然是一个不同的问题:动态类型的编程语言是故意设计的,因为它允许更多的灵活性。