I guess the only "downside" of using a difference list version of append
instead of the classical append/3
is that you maybe don't have a difference list to start with. In other words, if you have a list like this:
[a,b,c]
instead of this:
[a,b,c|Back]
... then you must traverse it if you want to append something to its end.
As long as you are going to append lists to each other, you will indeed use difference lists from the start. BTW, in modern Prolog separate arguments for the list and the rest are usually used. Here are a few SWI-Prolog library predicates that use difference lists:
This list is definitely not exhaustive. The point is that these predicates, by using difference lists, allow you to add more to the back of the list in constant time.
As soon as you find yourself using append/3
for appending lists in a program you are writing, you should consider using difference lists instead. As for downsides, as long as you don't need to append lists, you need to carry around an extra argument that you don't use for anything: then, a just a list is good enough.
Furthermore, append/3
, as it is usually implemented, can do many more things than just append lists. Try the following queries:
?- append(A, B, [1,2,3]). % split a list
?- append([1,2,3], Back, List). % make a difference list
?- append(Prefix, _, [a,b,c,d]). % list prefix
?- append(_, Suffix, [a,b,c,d]). % list suffix
and so on.
One nice example of a good use of difference lists are Prolog queues (see at the bottom of this answer, example is stolen, in simplified form, from "The Craft of Prolog" by Richard O'Keefe).
Afterword
While you might define a "difference list append" predicate, usually it is unnecessary, as it only adds a level of indirection. Say I'm writing a predicate that takes a binary tree and gives you the difference list (list and its back) resulting from in-order traversal:
% tree_inorder_list(+Tree, -List, -Back)
tree_inorder_list(nil, List, List).
tree_inorder_list(t(X, L, R), List, Back) :-
tree_inorder_list(L, List, [X|List0]),
tree_inorder_list(R, List0, Back).
Using a separate predicate for appending would only add code.
And yes, this could be written as a DCG instead! It looks much better:
tree_inorder(nil) --> [].
tree_inorder(t(X, L, R)) -->
tree_inorder(L),
[X],
tree_inorder(R).
Instead of querying:
?- tree_inorder_list(Tree, List, []).
you would have to query, for the DCG version:
?- phrase(tree_inorder(T), List).
You can use listing/1
to see how this DCG definition translates to a Prolog predicate:
?- listing(tree_inorder//1).
tree_inorder(nil, A, A).
tree_inorder(t(D, A, E), B, G) :-
tree_inorder(A, B, C),
C=[D|F],
tree_inorder(E, F, G).
true.
This is identical to the difference list definition above (except for one unification, C=[D|F]
, which is not inlined).
And because there is a difference-list phrase/3
, you can use tree_inorder//1
on more than one tree in the difference list mode, and concatenate the lists in constant time:
?- list_to_search_tree([c,a,b], A),
phrase(tree_inorder(A), List, Back),
list_to_search_tree([z,y,z,z,x], B),
phrase(tree_inorder(B), Back).
A = t(b, t(a, nil, nil), t(c, nil, nil)),
B = t(y, t(x, nil, nil), t(z, nil, nil)),
List = [a, b, c, x, y, z],
Back = [x, y, z].
(Thank you to @WillNess for suggesting this addition)