We present a clpfd-ish variant of
length/2
that's tailored to @mat's clpfd implementation.
:- use_module(library(clpfd)).
:- use_module(library(dialect/sicstus)).
:- multifile clpfd:run_propagator/2.
The "exported" predicate lazy_len/2
is defined like this:
lazy_len(Es, N) :-
N in 0..sup, % lengths are always non-negative integers
lazylist_acc_len(Es, 0, N),
create_mutable(Es+0, State),
clpfd:make_propagator(list_FD_size(State,N), Propagator),
clpfd:init_propagator(N, Propagator),
clpfd:trigger_once(Propagator).
The global constraint handler list_FD_size/3
incrementally modifies its internal state as constraint propagation occurs. All modifications are trailed and are un-done upon backtracking.
clpfd:run_propagator(list_FD_size(State,N), _MState) :-
get_mutable(Es0+Min0, State),
fd_inf(N, Min),
Diff is Min - Min0,
length(Delta, Diff),
append(Delta, Es, Es0),
( integer(N)
-> Es = []
; Delta = []
-> true % unchanged
; update_mutable(Es+Min, State)
).
lazy_len/2
tackles the problem from two sides; the clpfd constraint part of it was shown above. The tree side uses prolog-coroutining to walk down the list as far as the partial instantiation allows1:
lazylist_acc_len(_, _, N) :-
integer(N),
!.
lazylist_acc_len(Es, N0, N) :-
var(Es),
!,
when((nonvar(N);nonvar(Es)), lazylist_acc_len(Es,N0,N)).
lazylist_acc_len([], N, N).
lazylist_acc_len([_|Es], N0, N) :-
N1 is N0+1,
N in N1..sup,
lazylist_acc_len(Es, N1, N).
Sample queries:
?- lazy_len(Xs, N).
when((nonvar(N);nonvar(Xs)), lazylist_acc_len(Xs,0,N)),
N in 0..sup,
list_FD_size(Xs+0, N).
?- lazy_len(Xs, 3).
Xs = [_A,_B,_C].
?- lazy_len([_,_], L).
L = 2.
?- lazy_len(Xs, L), L #> 0.
Xs = [_A|_B],
when((nonvar(L);nonvar(_B)), lazylist_acc_len(_B,1,L)),
L in 1..sup,
list_FD_size(_B+1, L).
?- lazy_len(Xs, L), L #> 2.
Xs = [_A,_B,_C|_D],
when((nonvar(L);nonvar(_D)), lazylist_acc_len(_D,3,L)),
L in 3..sup,
list_FD_size(_D+3, L).
?- lazy_len(Xs, L), L #> 0, L #> 2.
Xs = [_A,_B,_C|_D],
when((nonvar(L);nonvar(_D)), lazylist_acc_len(_D,3,L)),
L in 3..sup,
list_FD_size(_D+3, L).
And, at long last, one more query... well, actually two more: one going up—the other going down.
?- L in 1..4, lazy_len(Xs, L), labeling([up], [L]).
L = 1, Xs = [_A]
; L = 2, Xs = [_A,_B]
; L = 3, Xs = [_A,_B,_C]
; L = 4, Xs = [_A,_B,_C,_D].
?- L in 1..4, lazy_len(Xs, L), labeling([down], [L]).
L = 4, Xs = [_A,_B,_C,_D]
; L = 3, Xs = [_A,_B,_C]
; L = 2, Xs = [_A,_B]
; L = 1, Xs = [_A].
Footnote 1:
Here, we focus on preserving determinism (avoid the creation of choice-points) by using delayed goals.
length/2
we would lose that "100% Prolog"-property... how can the coding effort be kept in reasonable bounds? – Gertrudislength(Xs,N) :- ..., var(N), clpfd:fd_sup(N,Sup), integer(Sup), !, ... .
Do other Prolog processors like yap have similar "overloading" capabilities? – Gertrudis