Many systems provide a pure and efficient implementation of member/2
. In particular, no choice point is left open for:
?- member(b,[a,b]).
true.
whereas, a naive implementation of member/2
produces rather:
?- member(b,[a,b]).
true ;
false.
which is certainly correct from a declarative viewpoint, but less efficient.
On the other hand, there are some technical problems with member/2
. It permits redundant solutions, like in:
?- member(a,[a,a]).
true ;
true.
memberd/2
solves this problem using if_/3
and (=)/3
.
memberd(E, [X|Xs]) :-
if_(E = X, true, memberd(E, Xs)).
?- memberd(a,[a,a]).
true.
Unfortunately, this definition leaves choice points open again - producing ; false
("leftover choicepoints") in situations where member does not:
?- memberd(X,[a,b]).
X = a ;
X = b ;
false. % BAD - to be avoided!
?- member(X,[a,b]).
X = a ;
X = b.
So my question: Is there a definition of memberd/2
that avoids the choice point as this one above?
In this answer we compare three different list-membership predicates:
member/2
, a builtin predicate, as implemented in SWI-Prolog.memberd/2
, as defined by the OP:memberd_new/2
, a proposed alternative which is defined like this:Let's go!
First, some ground queries:
Next, some queries having multiple distinct solutions:
Next, a test-case suggested in a comment to a previous answer which broke the version of
memberd_new/2
presented earlier.A variation of above test-case:
Last, some non-terminating queries:
There's more... Suppose, we implement
memberD/2
based onmemberd_t/3
:How does that compare to
memberd/2
, as defined by the OP in the question?Let's re-run some queries!
In above queries,
memberd/2
andmemberD/2
give the same answers and leave behind superfluous choice-points in the same instances.Let's dig a little deeper! Consider the following queries using
memberd_t/3
with corner-cases:This is not quite what I expected / wanted to get. What can we do? Basically, I see two options:
Accept this discrepancy and proclaim: "These queries are corner-cases of no importance."
Build an implementation of
memberd_t/3
that can handle these cases.Both options have strengths and weaknesses. In the following we implement
memberd_new_t/3
which both handles the corner cases and reduces the creation of superfluous choice-points.Warning: ugly code ahead!
Let's check if we produce fewer useless choicepoints with
memberd_new_t/3
!Alright! So what about above corner cases?
It works! At last, consider the most general queries:
For these corner cases,
memberd_new_t/3
is more likememberd/3
thanmemberd_t/3
:First, we rename
memberd
tomemberd_old
for the sake of clarity.Then, we implement
memberd_new/2
, which uses lagging and 1st argument indexing to prevent the creation of the useless choicepoint at the end of the list.Let's compare
member/2
(SWI-Prolog builtin predicate),memberd_old/2
, andmemberd_new/2
!First, a ground query:
Next, another ground query:
Now, a query having multiple distinct solutions:
Edit
The implementation of
memberd_new/2
presented here is deprecated.I recommend using the newer implementation shown in this answer.