你的代码有几个特性使得初学者很难理解它。特别是,使用模式化,低级别的算术是一种严重的障碍,当与一个有趣的(事实上也是一种严肃的)方式与程序交互时。
例如,要了解关系,从最常用的查询开始很有用。这只会问“是否有任何解决方案,如果有的话,解决方案是什么样的?”。在您的具体的例子,最一般的查询是这样的:
?- insertAt(E, Pos, Ls0, Ls).
,这几乎立即产生一个instantiation error
由于非陈述性算术谓词使用的是:
?- insertAt(E, Pos, Ls0, Ls).
Pos = 0,
Ls = [E|Ls0] ;
ERROR: insertAt/4: Arguments are not sufficiently instantiated
此外,你是通过使用命令名称(“insert ...”)来阻止一个很好的声明式阅读。这使得开发关系型编程的感觉不必要的困难。因此,我建议你:(1)使用更具说明性的谓词名称,(2)使用自然数的符号表示法,使谓语更容易理解和更一般化。
我将使用数字0
表示零,术语s(X)
表示数字X
的后继数字。有关此表示的更多信息,请参见successor-arithmetics。
有了这些变化,代码变为:
element_at(E, 0, [_|Ls], [E|Ls]).
element_at(E, s(X), [L|Ls0], [L|Ls]) :-
element_at(E, X, Ls0, Ls).
要了解这个程序,我们声明阅读:第一句是真如果位置是0
,最后头清单E
,尾巴......等等。第二句话是真如果element_at(E, X, Ls0, Ls)
成立,头...等
得很漂亮,最一般的查询现在的效果要好得多:
?- element_at(E, Pos, Ls0, Ls).
Pos = 0,
Ls0 = [_G1071|_G1072],
Ls = [E|_G1072] ;
Pos = s(0),
Ls0 = [_G1073, _G1079|_G1080],
Ls = [_G1073, E|_G1080] ;
Pos = s(s(0)),
Ls0 = [_G1073, _G1081, _G1087|_G1088],
Ls = [_G1073, _G1081, E|_G1088] .
注意,虽然有一些不公平的事情在这里:如果是其余位置的答案?为了更公平的枚举,我们用length/2
,事先说明,我们正在考虑一个又一个列表的长度:
?- length(Ls0, _), element_at(E, Pos, Ls0, Ls).
Ls0 = [_G1124],
Pos = 0,
Ls = [E] ;
Ls0 = [_G1124, _G1127],
Pos = 0,
Ls = [E, _G1127] ;
Ls0 = [_G1124, _G1127],
Pos = s(0),
Ls = [_G1124, E] .
,现在它更清楚如何在不同的参数进行交互,因为你已经看到条款的各种例子说由谓词描述。
事实上,以减少我们需要跟踪的参数的个数和变量名,我描述列表时经常使用DCG notation,我想向你展示这种替代版本太多:
element_at(Element, 0, [_|Ls]) -->
[Element],
list(Ls).
element_at(Element, s(X), [L|Ls]) -->
[L],
element_at(Element, X, Ls).
list([]) --> [].
list([L|Ls]) --> [L], list(Ls).
?- length(Ls0, _), phrase(element_at(E, Pos, Ls0), Ls).
Ls0 = [_G1148],
Pos = 0,
Ls = [E] ;
Ls0 = [_G1148, _G1151],
Pos = 0,
Ls = [E, _G1151] ;
Ls0 = [_G1148, _G1151],
Pos = s(0),
Ls = [_G1148, E] .
一旦你阅读了dcg表示法,这个版本将会变得清晰。
最后,你可能会说:“嗯,这是很好的,但s(X)
符号似乎仍然很奇怪”,你可能想使用整数更广泛使用的印度 - 阿拉伯数字在你的程序中。为此,我们可以简单地从上面的任意一个版本中取代s(X)
表示法,并用CLP(FD)约束条件的声明性整数算术替代s(X)
表示法。例如,第一个版本:
:- use_module(library(clpfd)).
element_at(E, 0, [_|Ls], [E|Ls]).
element_at(E, X, [L|Ls0], [L|Ls]) :-
X #> 0,
X #= X0 + 1,
element_at(E, X0, Ls0, Ls).
这也适用于所有的方向,正是因为我们从一个很好的陈述和一般谓词期待:
?- length(Ls0, _), element_at(E, Pos, Ls0, Ls).
Ls0 = [_G2095],
Pos = 0,
Ls = [E] ;
Ls0 = [_G2095, _G2098],
Pos = 0,
Ls = [E, _G2098] ;
Ls0 = [_G2095, _G2098],
Pos = 1,
Ls = [_G2095, E] .
请参阅clpfd更多的信息,我希望这篇文章鼓励你更多地关注你的Prolog代码,尝试在各个方向上,并且以声明的方式阅读它。 (正在描述什么?)
+1。使用'nnth0/4'的解决方案可能是实践中应该做的。它也可以像我预期的任何参数实例化模式那样运行。 – 2015-10-15 10:24:11