Term expansion for a list of terms
Asked Answered
P

1

5

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)

Pneumograph answered 8/8, 2014 at 12:8 Comment(3)
The example from the SWI-Prolog documentation does not apply fully: It enumerates all cases exhaustively and does not need a default case.Imes
@Imes yes, but what thought I might want is to enumerate the possible arguments for the default case....Pneumograph
If that's possible, then that's what I would do too.Imes
I
5

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:

  1. the term needs to be handled in a special way
  2. nothing special needs to be done with the term.

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.

Imes answered 8/8, 2014 at 12:30 Comment(2)
I like this suggestion (+1), but doesn't it imply that I need to explicitly treat my a and b and x and y in a previous step? In my particular case, I am parsing text; then, at that point I will have to have each of the default and special cases explicitly dealt with as well.... In effect, I am tagging the tags! Also, see the edited question for a precedent.Pneumograph
Yes, there will typically be a small early step in your programs where you convert defaulty structures to non-defaulty structures, usually directly after receiving user input (from files or the terminal). This part will often use non-monotonic control structures like if-then-else, cuts etc., but the nice thing is: Once your data is represented cleanly, it can be handled exclusively via pattern matching throughout the (large) remainder of your program! You are tagging your data, not the tags.Imes

© 2022 - 2024 — McMap. All rights reserved.