I figured out how to do this with a binary counter implemented in CLP(FD).
The idea is to take any number between 0
and 2^L-1
as a mask. If the mask divisible by 2, we omit the list element at that position, otherwise we include it. Then we divide the mask by 2 and continue to the next element in the list.
Backtracking over all possible masks gives us all possible sublists.
:- catch(use_module(library(clpz)), error(existence_error(_,_),_), use_module(library(clpfd))).
:- use_module(library(reif)).
mask_list_sublist(_, [], []).
mask_list_sublist(Mask, [H|T], Sublist) :-
length([H|T], L),
MaxMask #= 2^L - 1,
Mask in 0..MaxMask,
Include #= Mask mod 2,
if_(
Include = 1,
Sublist = [H|NextSublist],
Sublist = NextSublist
),
NextMask #= Mask // 2,
mask_list_sublist(NextMask, T, NextSublist).
list_sublist(List, Sublist) :-
mask_list_sublist(_, List, Sublist).
This is perfectly general and works regardless of whether the list is a set or not, and even if no arguments are instantiated:
?- list_sublist([a,b,c], S).
S = [a, b, c] ;
S = [a, b] ;
S = [a, c] ;
S = [a] ;
S = [b, c] ;
S = [b] ;
S = [c] ;
S = [].
?- list_sublist(L, S).
L = S, S = [] ;
L = S, S = [_49782] ;
L = [_49782],
S = [] ;
L = S, S = [_49782, _49794] ;
L = [_49782, _49794],
S = [_49782] ;
L = [_49782, _49794],
S = [_49794] ;
L = [_49782, _49794],
S = [] ;
L = S, S = [_49782, _49794, _49800] ;
L = [_49782, _49794, _49800],
S = [_49782, _49794] ;
L = [_49782, _49794, _49800],
S = [_49782, _49800] ;
L = [_49782, _49794, _49800],
S = [_49782] ;
L = [_49782, _49794, _49800],
S = [_49794, _49800] ;
L = [_49782, _49794, _49800],
S = [_49794] ;
L = [_49782, _49794, _49800],
S = [_49800] ;
L = [_49782, _49794, _49800],
S = [] ;
...