You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Let me preface this by saying that I am a complete beginner in logic programming, Python, Lisp and its variants, and especially Kanren, and I am impressed by miniKanren, microKanren, and this project.—Respect!
Actually, I was just looking for a very lean way to program in Prolog, implemented in pure Python. But with Prolog I lose my head as soon as a ! operator appears, so I was happy about relational programming. This was the way I became aware of Kanren and this project.—What I see and read now makes me very optimistic and makes me marvel at all the things you can do with it. So I just couldn't resist trying it out. And so I wanted to try out the second example in Logic programming, section Metalogic programming—and stumbled across the problem of negation.
As soon as negation is used in a context that is even slightly complex, e.g. multiple negation, nesting in logical conjunctions and disjunctions, etc., everyone gets confused very quickly.—I have an original sample of the PhD thesis by Martin F. Sohnius, one of the founders of supersymmetry (Haag-Łopuszański-Sohnius theorem), which deals with many Graßmann-valued objects that are multiplied anticommutatively. In his handwritten dedication, he wishes my doctoral supervisor: "Good luck with the minus signs!"
In short, no one should jump to conclusions about how to implement negation, especially not me.
How can the negation \+ approved_attends(Person, Meeting) be expressed in miniKanren? Obviously there is no equivalent, in particular no general negation of a relation. But there is a limited way to express negation: that of the disequality constraint, implemented in this project by the neq goal. So I wondered if I could find a way to get at least a negation of the fact container Relation(). On the way there, maybe I should first build a container for negated facts to get a feel for the thing in the first place.
As we all know, the logical conjunction is commutative. Since what's going on under the hood actually depends heavily on the order of the goals, it's worth testing:
# Test with goals in the other order>>>run(0, Person, not_approved_attends_alt(Person, 'tea_party'), attends(Person, 'tea_party'))
('mad_hatter', 'dormouse')
It still looks good.
So far we have kept the tea party constant as a literal. Now let's try it with a variable:
# Test with two logical variables>>>Meeting=var('Meeting')
>>>run(0, [Person, Meeting], attends(Person, Meeting), not_approved_attends_alt(Person, Meeting))
(['mad_hatter', 'tea_party'], ['dormouse', 'tea_party'])
Super.—And, because we are thorough, we also make sure that commutativity also works with two variables:
# Test with two logical variables and goals in the other order>>>run(0, [Person, Meeting], not_approved_attends_alt(Person, Meeting), attends(Person, Meeting))
()
Ooops…
Okay; while I have zero idea about logical programming, Python and miniKanren, I'm sure that logical conjunction is commutative, and as far as I understand the paradigm of relational programming (do I really?), the order of goals must have no effect on the result set, only on the order in which solutions are found—which is only relevant for very large result sets, especially infinitely large ones. What is also disturbing is that the result set shrinks when you remove a constraint (Meeting = 'tea_party').
Let's look at the state stream generated by the goal neq((Person, Meeting), ('alice', 'tea_party')), which is represented by not_approved_attends_alt:
As I understand this result (do I understand it correctly?), this is only one state, the one that is logically equivalent to the statement Person ≠ 'alice' ∧ Meeting ≠ 'tea_party'. And if I have understood this output correctly, then this is not correct. Rather, two states each with a ConstraintStore must be created, corresponding to the logical statement Person ≠ 'alice' ∨ Meeting ≠ 'tea_party'. Because this statement is logically equivalent to the negation of the statement Person = 'alice' ∧ Meeting = 'tea_party' (eq((Person, Meeting), ('alice', 'tea_party'))). —To check my understanding of the state stream, here is the state stream generated by Person ≠ 'alice' ∨ Meeting ≠ 'tea_party', formed with lany and neq:
So this is what the state stream of not_approved_attends_alt should look like.
Also the logical conjunction of several of the versions of neq((Person, Meeting), (..., ...)) "corrected" in this way—i.e. conjunctions of several neq of each a different variable—is obviously implemented logically incorrectly:
This result is also wrong, if I have misunderstood, because the second and fourth states are the same. In any case, this result does not correspond structurally to a conjunction of two disjunctions.
Thanks for the report! From a quick read, it does look like a genuine bug.
For reference, the constraint approach used here is based on the following fantastic paper by @jasonhemann: https://arxiv.org/abs/1701.00633. That explains how the constraints should be handled.
Let me preface this by saying that I am a complete beginner in logic programming, Python, Lisp and its variants, and especially Kanren, and I am impressed by miniKanren, microKanren, and this project.—Respect!
Actually, I was just looking for a very lean way to program in Prolog, implemented in pure Python. But with Prolog I lose my head as soon as a
!
operator appears, so I was happy about relational programming. This was the way I became aware of Kanren and this project.—What I see and read now makes me very optimistic and makes me marvel at all the things you can do with it. So I just couldn't resist trying it out. And so I wanted to try out the second example in Logic programming, section Metalogic programming—and stumbled across the problem of negation.As soon as negation is used in a context that is even slightly complex, e.g. multiple negation, nesting in logical conjunctions and disjunctions, etc., everyone gets confused very quickly.—I have an original sample of the PhD thesis by Martin F. Sohnius, one of the founders of supersymmetry (Haag-Łopuszański-Sohnius theorem), which deals with many Graßmann-valued objects that are multiplied anticommutatively. In his handwritten dedication, he wishes my doctoral supervisor: "Good luck with the minus signs!"
In short, no one should jump to conclusions about how to implement negation, especially not me.
Let's look at the example mentioned above:
How can the negation
\+ approved_attends(Person, Meeting)
be expressed in miniKanren? Obviously there is no equivalent, in particular no general negation of a relation. But there is a limited way to express negation: that of the disequality constraint, implemented in this project by theneq
goal. So I wondered if I could find a way to get at least a negation of the fact containerRelation()
. On the way there, maybe I should first build a container for negated facts to get a feel for the thing in the first place.First, I translated the example:
Cool.
To get something I can actually negate, namely an equality, I rephrased
approved_attends
:Okay, now the negation:
Looks good.
As we all know, the logical conjunction is commutative. Since what's going on under the hood actually depends heavily on the order of the goals, it's worth testing:
It still looks good.
So far we have kept the tea party constant as a literal. Now let's try it with a variable:
Super.—And, because we are thorough, we also make sure that commutativity also works with two variables:
Ooops…
Okay; while I have zero idea about logical programming, Python and miniKanren, I'm sure that logical conjunction is commutative, and as far as I understand the paradigm of relational programming (do I really?), the order of goals must have no effect on the result set, only on the order in which solutions are found—which is only relevant for very large result sets, especially infinitely large ones. What is also disturbing is that the result set shrinks when you remove a constraint (
Meeting
='tea_party'
).Let's look at the state stream generated by the goal
neq((Person, Meeting), ('alice', 'tea_party'))
, which is represented bynot_approved_attends_alt
:As I understand this result (do I understand it correctly?), this is only one state, the one that is logically equivalent to the statement
Person
≠'alice'
∧Meeting
≠'tea_party'
. And if I have understood this output correctly, then this is not correct. Rather, two states each with a ConstraintStore must be created, corresponding to the logical statementPerson
≠'alice'
∨Meeting
≠'tea_party'
. Because this statement is logically equivalent to the negation of the statementPerson
='alice'
∧Meeting
='tea_party'
(eq((Person, Meeting), ('alice', 'tea_party'))
). —To check my understanding of the state stream, here is the state stream generated byPerson
≠'alice'
∨Meeting
≠'tea_party'
, formed withlany
andneq
:So this is what the state stream of
not_approved_attends_alt
should look like.Also the logical conjunction of several of the versions of
neq((Person, Meeting), (..., ...))
"corrected" in this way—i.e. conjunctions of severalneq
of each a different variable—is obviously implemented logically incorrectly:This result is also wrong, if I have misunderstood, because the second and fourth states are the same. In any case, this result does not correspond structurally to a conjunction of two disjunctions.
Thank you for your attention.
The text was updated successfully, but these errors were encountered: