If I have a program like this and I want the last item for a person not to have a comma following it, how do I do this? Is it best to use a DCG? How would that work?
male(bob).
male(dave).
male(fred).
male(dereck).
likes(bob,cake).
likes(bob, pie).
likes(bob, apple).
likes(dave, fish).
likes(dave, meat).
likes(dave, potato).
likes(dave, pear).
likes(fred, water).
likes(fred, beer).
likes(dereck, wine).
likes(dereck, cake).
print:-
forall(
male(Person),
(
format("~w, ",[Person]),
forall(
likes(Person,Item),
format("~w, ",[Item])
),
format("~n~n",[])
)
).
The out put is:
bob, cake, pie, apple,
dave, fish, meat, potato, pear,
fred, water, beer,
dereck, wine, cake, %<I dont want these last commas
Consider first a pure solution, using DCGs to translate a list of things a person likes to a list of formatting instructions that can later be interpreted:
person_likes(Who, Whats) -->
atom(Who), atom(': '),
likes_(Whats).
likes_([]) --> [newline].
likes_([X]) --> atom(X), [newline].
likes_([X,Y|Rest]) --> atom(X), atom(', '), likes_([Y|Rest]).
atom(A) --> [atom(A)].
As you see, the key idea is to distinguish the cases depending on the number of elements in the list. This shows you how to solve such problems very generally.
You can already use it like this:
?- phrase(person_likes(bob, [cake,pie]), Instrs).
Instrs = [atom(bob), atom(': '), atom(cake), atom(', '), atom(pie), newline].
For the desired output, you simply interpret these formatting instructions. For example:
output(newline) :- nl.
output(atom(A)) :- format("~w", [A]).
Sample query, with your example facts:
?- male(M), findall(W, likes(M,W), Whats),
phrase(person_likes(M,Whats), Ts),
maplist(output, Ts), false.
Yielding:
bob: cake, pie, apple
dave: fish, meat, potato, pear
fred: water, beer
dereck: wine, cake
Of course, you can use the same pattern to obtain a shorter, impure version (using side-effects) in this concrete case. But you then cannot use it in the other direction! So, it is worthwhile to study the pure version.