Say I want to have a number of rules that all follow the same pattern. I have ran into this situation when I want to avoid non-deterministic behavior by explicitly listing all possible first arguments. I know, however, that I need to do exactly the same for some of the possibilities. One way to deal with it would be to have a catch-all clause at the end:
foo(a) :- /* do something */.
foo(b) :- /* do something else*/.
foo(_). /* ignore the rest */
but this is not very nice because I can't actually know if got unexpected input, or if I made a mistake in my program. To avoid this, I could also say
foo(X) :- memberchk(X, [ /* list of possible values of X */ ]).
but again, I am now fighting against Prolog's deterministic behavior and indexing when the argument is ground.
So, instead, I do something like this:
term_expansion(foos(Foos), Foo_rules) :-
maplist(expand_foo, Foos, Foo_rules).
expand_foo(Foo, foo(Foo)).
other_foos([x,y,z]).
The thing is, I sort of tried to find existing code like this and I couldn't. Is it because I am doing something wrong? Is there a better way to approach this problem? Or circumvent it altogether?
I don't mind answers that say "you are solving the wrong problem".
EDIT: Some googling actually got me to this very similar example from the SWI-Prolog documentation:
http://www.swi-prolog.org/pldoc/man?section=ext-dquotes-motivation (at the very bottom)
First a few comments on the variants you already suggest:
foo(a) :- /* do something */.
foo(b) :- /* do something else */.
foo(_). /* ignore the rest */
The main problem with this is that the final clause (foo(_)
) applies when the other - presumably more specialized - clauses also apply. So the query ?- foo(a).
is now unintentionally non-deterministic.
You say this version "is not very nice because I can't actually know if got unexpected input, or if I made a mistake in my program". We could guard against unexpected input by making sure, in the check-all clause, that the given term is not unexpected:
foo(a) :- /* do something */.
foo(b) :- /* do something else */.
foo(X) :- must_be(oneof([a,b,x,y], X).
This raises an error when the term is of an unexpected form. I used x
and y
as examples for terms that are not handled specially. Note that a
and b
must of course be included, because the clause (again) applies to both of them as well. The predicate is still not deterministic even when its argument is instantiated, because first argument indexing cannot distinguish the cases. You write "I am now fighting against Prolog's deterministic behavior and indexing when the argument is ground" and probably (and correctly) mean that you cannot benefit from these features when you use this representation.
And now the nice declarative solution: Every time you are tempted to introduce a "catch-all" or "default" clause, reconsider your data representation, and introduce distinguishing functors for the different cases that can apply. In your example, the two cases are:
I will use special(_)
and ordinary(_)
to distinguish the cases. Thus:
foo(special(S)) :- foo_special(S).
foo(ordinary(_)). % do nothing
foo_special(a) :- /* do something */
foo_special(b) :- /* do something else */
These predicates can be used in all directions, and are deterministic when the argument is known. Type-checks can be added easily.