In CLP it is important to separate constraints from nondeterminism
(search). In a Prolog-based framework, this distinction is not
readily apparent in the language, and this often confuses beginners.
Take a predicate like Prolog's length/2: while logically this can
be viewed as a "constraint" on the values of its arguments, in CLP
terminology we don't usually call this a constraint, because it will
nondeterministically generate values for its arguments whenever these
are not sufficiently instantiated.
The behaviour we expect from a constraint is more passive: it
should forbid its arguments taking values that the constraint
semantics doesn't allow, but not pro-actively generate arbitrary valid
values. The reason for that is that we usually want to set up a whole
network of constraints (constraint setup phase) before we start
exploring different variable assignments (search phase).
Now, constraint solvers come with a number of predefined constraints
over a certain variable domain (the most widely used being constraints over
integer variables). But you wanted to constrain a variable to take
values from the domain of lists. You don't have a ready-made
implementation for that, so you may want to write your own. CLP
systems differ in the support they have for letting you define your
own constraints. In the following examples I use ECLiPSe's delay
clause feature because that's probably the most readable (Disclaimer:
I'm personally involved with ECLiPSe).
Here is a simple length-constraint. Note the "delay clause" that
causes the predicate to wait if either argument is uninstantiated:
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).
This can ensure that a variable takes only a list of a certain length:
?- 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)
but when there isn't enough information, it waits (as indicated by
the "delayed goal" message):
?- c_len(Xs, 3), Xs = [_,_|Tail].
Xs = [_64, _66|Tail]
There is 1 delayed goal.
Yes (0.00s cpu)
In this last example we see that, although the constraint is correct (in
the sense that it doesn't allow invalid instantiations of its arguments),
it could actually be bit cleverer and infer that Tail
must be a list of length 1.
This is fairly typical: the operational behaviour of a constraint (often
referred to as its propagation strength) can vary between implementations.
Usually it reflects a tradeoff between propagation strength and
computational complexity.
Here is a somewhat better implementation. It takes advantage of finite
domain variables, and also implements your requirement of constraining
the domains of the list elements:
:- 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).
This can enforce your desired list properties (here length 1 to 3 and values
between 1 and 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)
It also does some early inferences, such as creating list elements with
appropriate integer domains:
?- bounded_list(Xs, 1, 5, 3).
Xs = [_319{1..5}, _472{1..5}, _625{1..5}]
Yes (0.00s cpu, ...)