我正在编写一个prolog程序来检查变量是否为整数。 我“回归”结果的方式很奇怪,但我认为回答我的问题并不重要。
我已经为这种行为写了传递单元测试;他们在这......
foo_test.pl
:- begin_tests('foo').
:- consult('foo').
test('that_1_is_recognised_as_int') :-
count_ints(1, 1).
test('that_atom_is_not_recognised_as_int') :-
count_ints(arbitrary, 0).
:- end_tests('foo').
:- run_tests.
这是通过这些测试的代码......
foo.pl
count_ints(X, Answer) :-
integer(X),
Answer is 1.
count_ints(X, Answer) :-
\+ integer(X),
Answer is 0.
测试正在通过,这很好,但是当我运行它时,我收到警告。这是运行测试时的输出......
?- ['foo_test'].
% foo compiled into plunit_foo 0.00 sec, 3 clauses
% PL-Unit: foo
Warning: /home/brandon/projects/sillybin/prolog/foo_test.pl:11:
/home/brandon/projects/sillybin/prolog/foo_test.pl:4:
PL-Unit: Test that_1_is_recognised_as_int: Test succeeded with choicepoint
. done
% All 2 tests passed
% foo_test compiled 0.03 sec, 1,848 clauses
true.
count_ints
将两个;
谓词合并为一个,但它仍会产生相同的警告。答案 0 :(得分:6)
首先,让我们忘记整个测试框架,只考虑顶层的查询:
?- count_ints(1, 1). true ; false.
此互动告诉您,在第一个解决方案之后,会留下选择点。这意味着需要尝试替代,并尝试回溯。在这种情况下,没有进一步的解决方案,但系统在实际尝试之前无法说明这一点。
all/1
选项有几种方法可以修复警告。一个直截了当的就是说出这样的测试用例:
test('that_1_is_recognised_as_int', all(Count = [1])) :- count_ints(1, Count).
这隐含地收集所有解决方案,然后立即对所有解决方案做出声明。
更智能的解决方案是让count_ints/2
本身具有确定性!
执行此操作的一种方法是使用if-then-else,如下所示:
count_ints(X, Answer) :- ( integer(X) -> Answer = 1 ; Answer = 0 ).
我们现在有:
?- count_ints(1, 1). true.
即,查询现在确定性地成功。
然而,最优雅的解决方案是使用清洁表示,以便您和Prolog引擎可以通过模式匹配
例如,我们可以将整数表示为i(N)
,将其他所有内容表示为other(T)
。
在这种情况下,我使用包装i/1
和other/1
来区分这些情况。
现在我们有:
count_ints(i(_), 1). count_ints(other(_), 0).
测试用例可能如下:
test('that_1_is_recognised_as_int') :- count_ints(i(1), 1). test('that_atom_is_not_recognised_as_int') :- count_ints(other(arbitrary), 0).
此操作也会在没有警告的情况下运行,和具有显着优势,即代码实际上可用于生成答案:
?- count_ints(Term, Count). Term = i(_1900), Count = 1 ; Term = other(_1900), Count = 0.
相比之下,我们有其他版本:
?- count_ints(Term, Count). Count = 0.
不幸的是,最多只能考虑覆盖50%的可能案例......
正如Boris在评论中正确指出的那样,我们可以通过约束将i/1
术语的参数强加到整数来使代码更加严格。例如,我们可以写:
count_ints(i(I), 1) :- I in inf..sup. count_ints(other(_), 0).
现在,参数必须是一个整数,通过以下查询变得清晰:
?- count_ints(X, 1). X = i(_1820), _1820 in inf..sup. ?- count_ints(i(any), 1). ERROR: Type error: `integer' expected, found `any' (an atom)
请注意,Boris提到的示例在没有更严格限制的情况下也失败了:
?- count_ints(X, 1), X = anything. false.
但是,在参数上添加更多约束通常很有用,如果需要对整数进行推理,CLP(FD)约束通常是明确表示类型约束的良好且通用的解决方案,否则这些约束仅隐含在程序中
请注意,integer/1
没有收到备忘录:
?- X in inf..sup, integer(X). false.
这表明,虽然X
在此示例中没有受到整数的限制,但integer(X)
仍未成功。因此,您不能使用integer/1
等谓词作为类型的可靠检测器。依靠模式匹配和使用约束来提高程序的通用性要好得多。
答案 1 :(得分:2)
首先要做的事情是:documentation of the SWI-Prolog Prolog Unit Tests package非常好。 Section 2.2. Writing the test body中解释了不同的模式。 2.2.1中的相关句子是:
确定性谓词是必须成功一次的谓词,对于表现良好的谓词,不留任何选择。 [强调我的]
在程序编程中,当你调用一个函数时,它可以返回一个值或一组值;它可以修改状态(本地或全局);无论它做什么,它都会完成一次。
在Prolog中,当您评估谓词时,会搜索证明树以寻找解决方案。可能有多个解决方案!假设你像这样使用between/3
:
对于 x = 1,在[0,1,2]中是 x 吗?
?- between(0, 2, 1).
true.
但你也可以问:
枚举所有 x ,使 x 在[0,1,2]中。
?- between(0, 2, X).
X = 0 ;
X = 1 ;
X = 2.
获得第一个解决方案X = 0
后,Prolog停止并等待;这意味着:
查询
between(0, 2, X)
至少有一个解决方案X = 0
。它可能还有其他解决方案;按;
,Prolog将在证明树中搜索下一个解决方案。
选择点是Prolog在找到解决方案后放入搜索树中的标记。它将从该标记继续搜索下一个解决方案。
警告"测试成功与选择点"的意思是:
Prolog发现的解决方案是测试预期的解决方案;然而,它留下了一个选择点,所以它不是"表现良好"。
你没有故意放在那里的选择点可能是个问题。没有详细说明,它们可以阻止某些优化并造成效率低下。这有点好,但有时只有第一个解决方案是你(程序员)想要的解决方案,而下一个解决方案可能会产生误导或错误。或者,着名的是,在给出一个有用的答案后,Prolog可以进入无限循环。
同样,如果您知道这一点,这很好:在评估此谓词时,您永远不会要求多个解决方案。您可以将其包装在once/1
中,如下所示:
?- once( between(0, 2, X) ).
或
?- once( count_ints(X, Answer) ).
如果其他人使用您的代码,但所有投注都已关闭。成功选择点可能意味着来自"还有其他有用的解决方案"没有更多的解决方案,这将失败"到其他解决方案,但不是你想要的那种"现在进入一个无限循环!"
对于特定示例:您有一个内置的integer/1
,无需离开选择点即可成功或失败。因此,count_ints/2
原始定义中的这两个条款与X
的任何值互斥:
count_ints(X, Answer) :-
integer(X), ...
count_ints(X, Answer) :-
\+ integer(X), ...
然而,Prolog并不知道。它只查看子句 heads ,这两个是相同的:
count_ints(X, Answer) :- ...
count_ints(X, Answer) :- ...
这两个脑袋是相同的,Prolog并不认为该子句会决定另一个子句是否值得尝试,所以即使第一个参数确实是一个整数,它也会尝试第二个子句(这是"选择点"在您收到的警告中),总是失败。
既然您知道这两个条款是互斥的,那么告诉Prolog忘记其他条款是安全的。您可以使用once/1
,如上所示。当第一个参数确实是一个整数时,您还可以剪切证明树的其余部分:
count_ints(X, 1) :- integer(X), !.
count_ints(_, 0).
完全相同的操作语义,但Prolog编译器可能更容易优化:
count_ints(X, Answer) :-
( integer(X)
-> Answer = 1
; Answer = 0
).
......就像席子里的回答一样。至于使用模式匹配,这一切都很好,但如果X
来自其他地方,而不是来自您自己编写的代码,那么您仍需要在某个时候进行此检查。你得到的结果如下:
variable_tagged(X, T) :-
( integer(X) -> T = i(X)
; float(X) -> T = f(X)
; atom(X) -> T = a(X)
; var(X) -> T = v(X)
% and so on
; T = other(X)
).
此时你可以按照mat的建议编写你的count_ints/2
,而Prolog会通过查看你的两个条款相互排斥的子句知道。
我once asked a question归结为相同的Prolog行为以及如何处理它。 mat的答案推荐采用相同的方法。我在答案下面的评论中的评论与答案本身一样重要(如果你至少写了真正的程序)。