It gets rather difficult to remember the names of the various procedures
written to implement deterministic and non-deterministic membership
with different kinds of matching. An alternative to having to remember
the several names is to write a more versatile membership procedure
that allows the matching to be specified in the arguments.
We want to be able to write goals that look like:
| ?- memb_ext(a, [a,b,a,b], =).
| ?- memb_ext(Var, [a,b,a,b], ==).
| ?- memb_ext(3, [4,2,5,1,3], <).
and so on.
One way to program this would be to write clauses for every type
of matching, eg:
% 1 terminating - =/2 condition
memb_ext(Elem, [Elem|_], =).
% 2 terminating - ==/2 condition
memb_ext(Elem, [Head|_], ==) :-
Elem == Head.
% 2 terminating - </2 condition
memb_ext(Elem, [Head|_], <) :-
Elem < Head.
etc.
This would be a time-consuming way of writing a program. A better
way would be to, in some way, use the third argument in the running
of the program. This is quite easy to do, providing we know a few
more built-in predicates.
The first thing to do is to check that the third argument of memb_ext/3
is instantiated. In fact, we can go further than this by insisting
that it is instantiated to an atom and, if so, calling memb_ext/3:
% 1
memb_ext(Elem, List, Relation) :-
atom(Relation),
memb_ext1(Elem, List, Relation).
memb_ext1/3 implements the membership relation. The simpler clause
is the recursive one, which has the same pattern as the basic memb/3
procedure:
% 2 recursive
memb_ext1(Elem, [_|List], Relation) :-
memb_ext1(Elem, List, Relation).
The terminating condition is lengthier. Here we use three built-ins:
functor/3, arg/3 and call/1:
% 1 terminating
memb_ext1(Elem, [Head|_], Relation) :-
functor(Relation1, Relation, 2),
arg(1, Relation1, Elem),
arg(2, Relation1, Head),
call(Relation1).
The line functor(Relation1, Relation, 2) has the effect of
forming whatever Relation is instantiated to into a structured
object with two arguments. As an example, consider the following:
| ?- functor(Object, fred, 2).
Object = fred(_A,_B) ? ;
no
| ?- functor(Obj, ==, 2).
Obj = _A==_B ? ;
no
The line arg(1, Relation1, Elem) has the effect of instantiating
the first argument of Relation1 to Elem. Hence the
following:
| ?- arg(1, _22768==_22769, a), write(_22768==_22769), nl.
a==_104
yes
The following line instantiates the second argument. The last line,
call(Relation1) merely executes Relation1 as a Prolog
goal.
To illustrate the working of these procedures, consider the following:
| ?- Elem = a, memb_ext(a, [a,b,a,b], =), Elem = Output.
Elem = a
Output = a ? ;
Elem = a
Output = a ? ;
no
| ?- memb_ext(Var, [a,b,a,b], ==).
no
| ?- Elem = 3, memb_ext(3, [4,2,5,1,3], <), Elem = Output.
Elem = 3
Output = 3 ? ;
Elem = 3
Output = 3 ? ;
no
Although the use of "templates" such as these is rather esoteric,
the built-ins used in the terminating rule of memb_ext1/3 are extremely
important in Prolog because they are widely used in building compilers
for specialised languages in Prolog.
|