You could take advantage of the Rest argument of phrase/3; and there is also sequence//2 from library(dcg/high_order). It would be enough to define:
length_list(N, L) -->
{ length(L, N) },
L. % yes don't need string//1
Then:
?- use_module(library(dcg/high_order)).
true.
?- phrase(sequence(length_list(2), Sublists), [a,b,c,d], Rest), !.
Sublists = [[a, b], [c, d]],
Rest = [].
?- phrase(sequence(length_list(2), Sublists), [a,b,c,d,e], Rest), !.
Sublists = [[a, b], [c, d]],
Rest = [e].
But of course it all depends on the use case.
EDIT: since there are some “likes” on this, I would like to point out two issues.
- As it stands, using
L
instead ofstring(L)
is indeed a bit slower. - Since sequence//2 leaves choice points, it will eventually run out of memory if the list we are parsing is long enough. Cutting on every matched prefix will avoid this.