Prolog - 参数未实例化

Prolog - arguments are not instantiated

我正在尝试了解 Prolog 的工作原理。我正在使用 SWI-Prolog。 这是一些代码:

forall(C1,C2) :- \+ (C1, \+ C2).

foo(N) :- N < 10.

bar(N) :- N > 5.

foobar(N) :- forall(foo(N),bar(N)).

如果我执行以下操作,它会产生所需的输出:

?- foobar(5).
false.

但是当我尝试查看所有可能的值时出现错误:

?- foobar(N).
ERROR: </2: Arguments are not sufficiently instantiated

这是怎么回事?

基本上你正在编写一个程序来检查全局含义:

forall N, if N < 10 then N > 5

并且您想知道 N 的哪个域。

现在您在序言中正确地将其重写为:

\+ ( N < 10, \+ ( N > 5 ) ).

但是如果您尝试将该查询提供给 prolog 解释器,它将输出错误:

?- \+ ( N < 10, \+ ( N > 5 ) ).
ERROR: </2: Arguments are not sufficiently instantiated

因为<的参数没有实例化。对于 N < 3 这样的简单查询,它也会发生同样的事情。当然,如果你在查询之前实例化它,问题就消失了:

?- N=5, \+ ( N < 10, \+ ( N > 5 ) ).
false.

(N=5 时不成立)

?- N=6, \+ ( N < 10, \+ ( N > 5 ) ).
N = 6.

(但它适用于 N=6)。

您还可以放置一个谓词,通过回溯为 N 生成多个赋值,代替 N=6:

?- between(1, 12, N), \+ ( N < 10, \+ ( N > 5 ) ).
N = 6 ;
N = 7 ;
N = 8 ;
N = 9 ;
N = 10 ;
N = 11 ;
N = 12.

但是对于一个大域来说,这将是非常低效的(它需要对域的每个元素进行回溯)。

如果您想对有限域(即整数)进行推理,可以按照@lurker 的建议使用CLPFD 库。这种方法更有效,因为它有关于区间推理、区间交集等等的规则。

您必须将 <>,\+ 替换为 CLP 运算符 #<#>#/\#\。让我们试试看:

?- use_module(library(clpfd)).
?- #\ ( N #< 10 #/\ #\ ( N #> 5 ) ).
N+1#=_G11193,
N#>=6#<==>_G11205,
10#>=_G11193#<==>_G11217,
_G11217 in 0..1,
_G11217#/\_G11244#<==>0,
_G11244 in 0..1,
#\_G11205#<==>_G11244,
_G11205 in 0..1.

这可能有点难读,但在许多其他事情中,它会告诉您正在寻找的答案,即 N 的域:N #>= 6.

TL;DR. Prolog 具有良好的代数特性,只要您坚持其逻辑纯核心。


有关详细信息,请阅读 Whosebug 上对 Prolog 问题的以下出色回答:

  • Features of good Prolog code?
  • What is the logical 'not' in Prolog?
  • Are cuts that bad in programming?
  • Knowing when to use cut in prolog
  • Prolog without if and else statements
  • Using \== or dif
  • Why do we use '!' in prolog
  • How to prevent Prolog from backtracking where it shouldn't