在 CLP 中,将约束与不确定性(搜索)分开是很重要的。在基于 Prolog 的框架中,这种区别在语言中并不明显,这常常使初学者感到困惑。
以 Prolog 的length/2之类的谓词为例:虽然从逻辑上讲,这可以被视为对其参数值的“约束”,但在 CLP 术语中,我们通常不将其称为约束,因为它会在任何时候不确定地为其参数生成值这些没有充分实例化。
我们对约束的期望行为更加被动:它应该禁止其参数采用约束语义不允许的值,但不主动生成任意有效值。原因是我们通常希望在开始探索不同的变量分配(搜索阶段)之前建立一个完整的约束网络(约束设置阶段)。
现在,约束求解器在某个变量域上带有许多预定义的约束(最广泛使用的是整数变量的约束)。但是您想约束一个变量以从列表域中获取值。您没有现成的实现,因此您可能想要编写自己的实现。CLP 系统在支持您定义自己的约束方面有所不同。在以下示例中,我使用ECLiPSe的延迟子句功能,因为这可能是最易读的(免责声明:我个人参与了 ECLiPSe)。
这是一个简单的长度约束。请注意如果任一参数未实例化,则导致谓词等待的“延迟子句”:
delay c_len(Xs, N) if var(Xs) ; var(N).
c_len([], 0).
c_len([_|Xs], N) :-
N1 is N-1,
c_len(Xs, N1).
这可以确保一个变量只需要一个特定长度的列表:
?- c_len(Xs, 3), Xs = [_,_].
No (0.00s cpu)
?- c_len(Xs, 3), Xs = [_,_,_].
Xs = [_76, _78, _80]
Yes (0.00s cpu)
?- c_len(Xs, 3), Xs = [_,_,_,_].
No (0.00s cpu)
但是当没有足够的信息时,它会等待(如“延迟目标”消息所示):
?- c_len(Xs, 3), Xs = [_,_|Tail].
Xs = [_64, _66|Tail]
There is 1 delayed goal.
Yes (0.00s cpu)
在最后一个示例中,我们看到,尽管约束是正确的(从某种意义上说,它不允许对其参数进行无效实例化),但它实际上可以更聪明一点,并推断出它Tail
必须是长度为 1 的列表。这是相当典型:约束的操作行为(通常称为其传播强度)可能因实现而异。通常它反映了传播强度和计算复杂性之间的权衡。
这是一个更好的实现。它利用了有限域变量,还实现了约束列表元素域的要求:
:- use_module(library(ic)). % provides finite domain solver
delay bounded_list(Xs,_Lo,_Hi,Len) if var(Xs), var(Len).
bounded_list([],_Lo,_Hi, 0).
bounded_list([X|Xs], Lo, Hi, Len) :-
Len #> 0,
Lo #=< X, X #=< Hi,
Len1 #= Len-1,
bounded_list(Xs, Lo, Hi, Len1).
这可以强制执行您想要的列表属性(此处长度为 1 到 3,值介于 1 到 5 之间):
?- N #:: 1..3, bounded_list(Xs,1,5,N), Xs = [1,2,3].
N = 3
Xs = [1, 2, 3]
Yes (0.00s cpu)
?- N #:: 1..3, bounded_list(Xs,1,5,N), Xs = [1,2,3,4].
No (0.00s cpu)
?- N #:: 1..3, bounded_list(Xs,1,5,N), Xs = [1].
N = 1
Xs = [1]
?- N #:: 1..3, bounded_list(Xs,1,5,N), Xs = [1,9].
No (0.00s cpu)
它还进行了一些早期推断,例如创建具有适当整数域的列表元素:
?- bounded_list(Xs, 1, 5, 3).
Xs = [_319{1..5}, _472{1..5}, _625{1..5}]
Yes (0.00s cpu, ...)