byx*_*xor 3 unit-testing prolog plunit
我正在编写一个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.
Run Code Online (Sandbox Code Playgroud)
这是通过这些测试的代码......
foo.pl
count_ints(X, Answer) :-
integer(X),
Answer is 1.
count_ints(X, Answer) :-
\+ integer(X),
Answer is 0.
Run Code Online (Sandbox Code Playgroud)
测试正在通过,这很好,但我在运行它时会收到警告.这是运行测试时的输出...
?- ['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.
Run Code Online (Sandbox Code Playgroud)
count_ints
谓词组合成一个,使用;
,但仍然会产生相同的警告.首先,让我们忘记整个测试框架,只考虑顶层的查询:
?- 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
作为类型的可靠检测器.依靠模式匹配和使用约束来提高程序的通用性要好得多.
小智 7
首先要做的是:SWI-Prolog Prolog 单元测试包的文档非常好。不同的模式在第 2.2 节中解释。编写测试体。2.2.1中的相关语句是:
确定性谓词是必须只成功一次的谓词,对于表现良好的谓词,不留下任何选择点。[强调我的]
在过程编程中,当你调用一个函数时,它可以返回一个值,或者一组值;它可以修改状态(本地或全局);无论它做什么,它都会做一次。
在 Prolog 中,当您评估谓词时,会搜索证明树以寻找解决方案。可能有不止一种解决方案!假设你between/3
像这样使用:
对于x = 1,x在 [0, 1, 2] 中吗?
?- between(0, 2, 1).
true.
Run Code Online (Sandbox Code Playgroud)
但你也可以问:
枚举所有x使得x在 [0, 1, 2] 中。
?- between(0, 2, X).
X = 0 ;
X = 1 ;
X = 2.
Run Code Online (Sandbox Code Playgroud)
得到第一个解后X = 0
,Prolog停止等待;这意味着:
该查询
between(0, 2, X)
至少有一个解决方案,X = 0
。它可能有进一步的解决方案;按下;
,Prolog 将在证明树中搜索下一个解决方案。
在选择点是把序言搜索树中找到解决办法后的标志。它将继续从该标记搜索下一个解决方案。
警告“选择点测试成功”意味着:
Prolog 找到的解决方案是测试预期的解决方案;然而,它在那里留下了一个选择点,所以它不是“乖巧”。
你不是故意放在那里的选择点可能是一个问题。无需详细说明,它们可以防止某些优化并造成效率低下。这没什么,但有时只有第一个解决方案是您(程序员)想要的解决方案,而下一个解决方案可能会产生误导或错误。或者,众所周知,在给您一个有用的答案后,Prolog 可以进入无限循环。
同样,如果你知道这很好:当你评估这个谓词时,你永远不会要求一个以上的解决方案。您可以将其包装在 中once/1
,如下所示:
?- once( between(0, 2, X) ).
Run Code Online (Sandbox Code Playgroud)
或者
?- once( count_ints(X, Answer) ).
Run Code Online (Sandbox Code Playgroud)
如果其他人使用您的代码,尽管所有赌注都已关闭。在选择点上取得成功可能意味着从“还有其他有用的解决方案”到“没有更多解决方案,现在将失败”到“其他解决方案,但不是您想要的那种”再到“现在进入无限循环!”
对于特定示例:您有一个内置的integer/1
,它会成功或失败而不会留下选择点。因此,您原始定义中的这两个子句count_ints/2
对于 的任何值都是互斥的X
:
count_ints(X, Answer) :-
integer(X), ...
count_ints(X, Answer) :-
\+ integer(X), ...
Run Code Online (Sandbox Code Playgroud)
然而,Prolog 并不知道这一点。它只查看子句标题,这两个是相同的:
count_ints(X, Answer) :- ...
count_ints(X, Answer) :- ...
Run Code Online (Sandbox Code Playgroud)
这两个头是相同的,Prolog 没有进一步看子句头来决定另一个子句是否值得尝试,所以即使第一个参数确实是一个整数,它也会尝试第二个子句(这是“选择点”在您收到的警告中),并且总是失败。
既然您知道这两个子句是互斥的,那么告诉 Prolog 忘记另一个子句是安全的。您可以使用once/1
,如上所示。当第一个参数确实是整数时,您还可以切割证明树的其余部分:
count_ints(X, 1) :- integer(X), !.
count_ints(_, 0).
Run Code Online (Sandbox Code Playgroud)
完全相同的操作语义,但 Prolog 编译器可能更容易优化:
count_ints(X, Answer) :-
( integer(X)
-> Answer = 1
; Answer = 0
).
Run Code Online (Sandbox Code Playgroud)
......就像垫子的答案一样。至于使用模式匹配,这一切都很好,但是如果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)
).
Run Code Online (Sandbox Code Playgroud)
在这一点上,您可以count_ints/2
按照 mat 的建议编写您的内容,Prolog 将通过查看子句标题知道您的两个子句是互斥的。
我曾经问过一个问题,归结为相同的 Prolog 行为以及如何处理它。mat 的答案推荐了相同的方法。mat 对答案下方我的评论的评论与答案本身一样重要(至少如果您正在编写真正的程序)。
归档时间: |
|
查看次数: |
1338 次 |
最近记录: |