different(Xs, Ys) :-
member(X, Xs),
non_member(X, Ys).
different(Xs, Ys) :-
member(Y, Ys),
non_member(Y, Xs).
While this definition using member/2
and non_member/2
is almost1 perfect from a declarative viewpoint, it produces redundant solutions for certain queries and leaves choice points all around.
What is a definition that improves upon this (in a pure manner probably using if_/3
and (=)/3
) such that exactly the same set of solutions is described by different/2
but is determinate at least for ground queries (thus does not leave any useless choice points open) and omits (if possible) any redundant answer?
1
Actually, different([a|nonlist],[]), different([],[b|nonlist])
succeeds. It could equally fail. So a solution that fails for both is fine (maybe even finer).
Next contestant to the code beauty pageant!-)
This answer shows a refactored variation of code shown in a previous answer. It uses reified conjunction and disjunction:
Note the two versions for both "and" and "or"; the ones with suffix
_t
have an extra argument for the truth value, the ones without the suffix do not and assume thatTruth=true
should hold.Based on
and_t/3
and on reified term inequality predicatedif/3
, we definenonmember_t/3
:Now, let's define
some_absent_t/3
,different_t/3
anddifferent/2
, like so:Does it still run?
Looks alright!
All in all, not a huge improvement over existing answers, but IMO somewhat more readable code, and a reified version of
different/2
as an added bonus!The following bold bounty (+500) was offered not too long ago:
Challenge accepted! This answer is a follow-up to this previous answer.
We use the reified logical connective
(;)/3
, which can be defined like this:Next, we define the meta-predicate
call_/1
. It is useful with reified predicates used in this answer. With its name and semantics,call_/1
followsif_/3
,and_/2
, andor_/2
!Using
(;)/3
,call_/1
, andsome_absent_t/3
we implementdifferent/2
:Done! That's it.
Let's re-run the queries we used in previous answers!
Same queries, same answers... Looks alright to me!
First try!
The following code is based on the meta-predicates
tfilter/3
andtpartition/4
, the monotone if-then-else control constructif_/3
, the reified unary logical connectivenot_t/3
, and the reified term equality predicate(=)/3
:Sample query:
Let's observe determinism when working with ground data:
The above approach feels like a step in the right direction... But, as-is, I wouldn't call it perfect.
Conerning solutions that use if_, I would say an alternative approach would be to use constructive negation from the beginning. Constructive negation was researched already in the 80's, a pioneer was David Chan, and still pops up from time to time.
Assume we have a Prolog interpreter which has a constructive negation
(~)/2
. This constructive negation(~)/2
can be used to define a declarative if-then-else as follows, lets call this operator(~->)/2
:If the Prolog interpreter has also embedded implication
(=>)/2
besides constructive negation, one could alternatively define a declarative if-then-else as follows, lets call this operator(~=>)/2
:Note the switch from disjunction
(;)/2
to conjunction(,)/2
. Ask the Binary Decision Diagram (BDD) people why they are logically equivalent. Procedurally there are nuances, but through the backdoor of embedded implication for certain A, the second if-then-else variant will also introduce non-determinancy.But how would we go about and introduce for example constructive negation in a Prolog interpreter. A straight forward way would be to delegate constructive negation to a constraint solver. If the constraint solver has reified negation this can be done as follows:
But there not so many constraint solvers around, that would permit a sensible use for examples such as
member/2
etc.. Since often they provide reified negation only for domains such as finite integers, rationals, etc.. But for a predicate such asmember/2
we would need reified negation for the Herbrand universe.Also note that the usual approaches for constructive negation also assume that the ordinary rule implication gets another reading. This means that usually under constructive negation, we could pick the ordinary
member/2
definition, and get query results such as:But again the reified negation will hardly easily work with defined predicates, so that the following query will probably not work:
If the above succeeds than any of the declarative if-then-else such as
(~->)/2
or(~=>)/2
will have less frequent use, since ordinary predicate definitions will already deliver declarative interpretation by the Prolog interpreter. But why is constructive negation not wide spread? One reason might be the large design space of such a Prolog interpreter. I noticed that we would have the following options:Backward Chaining: We would basically spit the vanilla interpreter
solve/1
into two predicatessolvep/1
andsolven/1
.solvep/1
is responsible for solving positive goals andsolven/1
is responsible for solving negative goals. When we try this we will sooner or later see that we need more careful treatment of quantifiers and probably end up with a quantifier elimination method for the Herbrand domain.Forward Chaining: We will also notice that in backward chaining we will run into problems for the modelling of clauses with disjunction or existential quantifier in the head. This has to do with the fact that the sequent calculus suitable for Prolog has only one formula on the right hand side. So either we go multi formula on the right hand side and will loose paraconsistency, or we use forward chaining.
Magic Sets: But forward chaining will polute the solution spaces in an uncontrolled way. So we might need some form of combination of forward and backward chaining. I don't mean by that only, that we should be able dynamically switch between the two during the solution process, but I mean that we nead also a means to generate sets that direct the forward chaining process.
More problems are also noted in this answer here. This doesn't mean that sooner or later a formula will be found, to fit all this problem and solution pairs together, but it might take some more time.
Bye
Let's take it to the limit---by the help of
list_nonmember_t/3
,exists_in_t/3
, andor_/2
!Here's another try! We utilize the monotone if-then-else control construct
if_/3
, in combination with the reified list membership predicatememberd_t/3
, and first argument indexing to avoid the creation of useless choice-points.First, we run a query that we expect to fail:
The following queries are similar to the failing query given above; each one has a single "different" item
x
placed at different indices in the first[1,2,3]
or the second list[2,3,1]
:OK! Let's run another (quite general) query that I used in my previous answer:
Compact! A big improvement over what I presented earlier!