There's more... Suppose, we implement memberD/2
based on memberd_t/3
:
memberD(X,Xs) :- memberd_t(X,Xs,true).
How does that compare to memberd/2
, as defined by the OP in the question?
Let's re-run some queries!
?- memberd(a,[a,a]), memberd(a,[a,b]), memberd(b,[a,b]),
memberD(a,[a,a]), memberD(a,[a,b]), memberD(b,[a,b]).
true. % all of these succeed deterministiaclly
?- memberd(X,[a,b]).
X = a ; X = b ; false. % could be better
?- memberD(X,[a,b]).
X = a ; X = b ; false. % could be better
?- memberd(a,[a|nonlist]), memberD(a,[a|nonlist]).
true. % both succeed deterministically
?- memberd(X,[a|nonlist]).
X = a ; false. % could be better
?- memberD(X,[a|nonlist]).
X = a ; false. % could be better
In above queries, memberd/2
and memberD/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:
?- memberd_t(a,[a|nonlist],T).
T = true. % OK
?- memberd_t(b,[a|nonlist],T).
false. % missing: `T = false`
?- memberd_t(X,[a|nonlist],T).
T = true, X = a
; false. % missing: `T = false, dif(X,a)`
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!
memberd_new_t(E,Xs0,Truth) :-
( Xs0 \= [_|_]
-> Truth = false
; Truth = false,
freeze(Xs0, Xs0\=[_|_])
; Xs0 = [X|Xs],
( Xs \= [_|_]
-> =(E,X,Truth)
; if_(E=X,Truth=true,memberd_new_t(E,Xs,Truth))
)
).
Let's check if we produce fewer useless choicepoints with memberd_new_t/3
!
?- memberd_t(X,[a,b],true).
X = a ; X = b ; false. % suboptimal
?- memberd_new_t(X,[a,b],true).
X = a ; X = b. % BETTER
?- memberd_t(X,[a|nonlist],true).
X = a ; false. % suboptimal
?- memberd_new_t(X,[a|nonlist],true).
X = a. % BETTER
Alright! So what about above corner cases?
?- memberd_t(a,[a|nonlist],T).
T = true. % OK
?- memberd_new_t(a,[a|nonlist],T).
T = true. % OK
?- memberd_t(b,[a|nonlist],T).
false. % BAD
?- memberd_new_t(b,[a|nonlist],T).
T = false. % OK
?- memberd_t(X,[a|nonlist],T).
T = true, X = a
; false. % BAD
?- memberd_new_t(X,[a|nonlist],T).
T = true, X=a
; T = false, dif(X,a). % OK
It works! At last, consider the most general queries:
?- memberd_t(X,Xs,T).
T=false, Xs = []
; T=true , Xs = [X |_A]
; T=false, Xs = [_A ], dif(X,_A)
; T=true , Xs = [_A, X |_B], dif(X,_A)
; T=false, Xs = [_A,_B ], dif(X,_A), dif(X,_B)
; T=true , Xs = [_A,_B, X|_C], dif(X,_A), dif(X,_B)
; T=false, Xs = [_A,_B,_C ], dif(X,_A), dif(X,_B), dif(X,_C)
...
?- memberd_new_t(X,Xs,T).
T=false, freeze(Xs,Xs\=[_|_])
; T=true , Xs = [ X |_A]
; T=false, freeze(_B,_B\=[_|_]), Xs = [_A |_B], dif(X,_A)
; T=true , Xs = [_A, X |_B], dif(X,_A)
; T=false, freeze(_C,_C\=[_|_]), Xs = [_A,_B |_C], dif(X,_A), dif(X,_B)
; T=true , Xs = [_A,_B, X|_C], dif(X,_A), dif(X,_B)
; T=false, freeze(_D,_D\=[_|_]), Xs = [_A,_B,_C|_D], dif(X,_A), dif(X,_B), dif(X,_C)
...
For these corner cases, memberd_new_t/3
is more like memberd/3
than memberd_t/3
:
?- memberd(X,Xs).
Xs = [ X |_A]
; Xs = [_A, X |_B], dif(X,_A)
; Xs = [_A,_B, X |_C], dif(X,_A), dif(X,_B)
; Xs = [_A,_B,_C, X |_D], dif(X,_A), dif(X,_B), dif(X,_C)
; Xs = [_A,_B,_C,_D, X|_E], dif(X,_A), dif(X,_B), dif(X,_C), dif(X,_D)
...