Edit I’ve put a neatened up version of this with Venn diagrams on the wiki I started with more emphasis on Prolog and less on classical maths.
I’ve been reading a couple of the classics on logic and struggling to fit the theory into Prolog semantics. Judging from various discussions here, gather I’m not alone, so thought I’d put up some notes.
Sets and Logic
Set-builder notation is something that seems have been invented since my formal education way back in the last century, and is a very handy way of combining logic and sets which I’ll get to shortly. But first a quick refresher on basic logic and set theory.
Using copula notation p∧q for and, p∨q for or, and my own invention p ≤ q rather than p ⇒ q for implication is a handy mnemonic that these are related to P ∩ Q for intersection, P ∪ Q for union, and P⊆ Q for subset. Equivalence p ⇔ q and P ≡ Q for most problems need to be expanded into p ≤ q ∧q ≤ p, and P⊆ Q ∩ Q ⊆P which I’ll elaborate on later.
The set equivalent of negation ¬p is complement Pc, and while we tend to think of De Morgan’s laws as the logic substitution rules ¬(p∧q) ⇔ ¬p∨¬q and ¬(p∨q) ⇔ ¬p∧¬q, interestingly he discovered them from writings on set theory in ancient Indian texts, which in our notation would be (P ∩ Q)c ≡ Pc ∪ Qc and (P ∪ Q)c ≡ Pc ∩ Qc
To show how to use the above maths in Prolog, I’m going to use examples I’ll use are taken from my translation of Jennifer Widom’s SQL basics examples which I’ve translated into Prolog SWISH -- SWI-Prolog for SHaring
I’ll be using these two tables in my examples
%! student(?SID:text, ?SName:text, ?GPA:float, ?SizeHS:integer) is nondet
student(123, 'Amy', 3.9, 1000).
student(234, 'Bob', 3.6, 1500).
student(345, 'Craig', 3.5, 500).
student(456, 'Doris', 3.9, 1000).
student(567, 'Edward', 2.9, 2000).
student(678, 'Fay', 3.8, 200).
student(789, 'Gary', 3.4, 800).
student(987, 'Helen', 3.7, 800).
student(876, 'Irene', 3.9, 400).
student(765, 'Jay', 2.9, 1500).
student(654, 'Amy', 3.9, 1000).
student(543, 'Craig', 3.4, 2000).
%! apply(?SID:integer, ?CName:text, ?Major:text, ?Decision:text) is nondet
apply(123, 'Stanford', 'CS', 'Y').
apply(123, 'Stanford', 'EE', 'N').
apply(123, 'Berkeley', 'CS', 'Y').
apply(123, 'Cornell', 'EE', 'Y').
apply(234, 'Berkeley', 'biology', 'N').
apply(345, 'MIT', 'bioengineering', 'Y').
apply(345, 'Cornell', 'bioengineering', 'N').
apply(345, 'Cornell', 'CS', 'Y').
apply(345, 'Cornell', 'EE', 'N').
apply(678, 'Stanford', 'history', 'Y').
apply(987, 'Stanford', 'CS', 'Y').
apply(987, 'Berkeley', 'CS', 'Y').
apply(876, 'Stanford', 'CS', 'N').
apply(876, 'MIT', 'biology', 'Y').
apply(876, 'MIT', 'marine biology', 'N').
apply(765, 'Stanford', 'history', 'Y').
apply(765, 'Cornell', 'history', 'N').
apply(765, 'Cornell', 'psychology', 'Y').
apply(543, 'MIT', 'CS', 'N').
Students IDs of those who have applied for CS
Basic set builder notation looks like P = {p|Φ(p)} where Φ(p) in this case is the boolean test Major == ‘CS’.
A simple way to do this is in Prolog is:
apply(SID, _, 'CS', _).
The above works, though it’s a little unelegant in that students 123 and 987 get listed twice because they applied for ‘CS’ at both Stanford and Berkeley. One way to sort that out is using distinct(SID, apply(SID, _, 'CS', _)).
Students IDs of those who have not applied for CS
A trap is to think this query is simply not ‘CS’ as in:
apply(SID, _, _Major, _), _Major \== 'CS'.
This gives the wrong answer because students 123 and 345 applied for ‘EE’ besides ‘CS’. The next mistake is to think \+apply(SID, _, 'CS', _).
will give us the complement of the apply(SID, _, 'CS', _).
whereas all it will return is false.
The way to make this work is
student(SID, _, _, _), \+apply(SID, _, 'CS', _).
In set-builder builder notation, Pc = {p ∈ E|p ∉ P}. When querying which students had applied for ‘CS’, we didn’t have to think of set E, which in this example is the student table. If we don’t include it in the not ‘CS’, we’ll fail to find the four students who are not in the apply table.
An important quirk of Prolog is that negation is not associative, ie we can’t rewrite our query \+apply(SID, _, 'CS', _), student(SID, _, _, _).
This crops up in implication shortly which is commonly written as ¬p∨q, which will only work in Prolog written q ∨¬p.
Students who have applied for both ‘CS’ and ‘EE’
This is the intersection of two sets of student IDs, which is easily done like so:
distinct(SID, (apply(SID, _, 'CS', _), apply(SID, _, 'EE', _))).
Students who have applied for both ‘CS’ or ‘EE’
In this example students who applied for ‘EE’ are a subset of students who applied for ‘CS’, so the union is the same as the ‘CS’ set.
There are two ways to or things in Prolog. One is to use a semicolon as in
distinct(SID, (apply(SID, _, 'CS', _); apply(SID, _, 'EE', _))).
The other way is to write the alternatives we want as separate rules.
How subsets and material implication are related
The implication rule in classical logic (sometimes called material implication) was something I really struggled to understand, and I had an “aha” moment recently when I grasped p ⇒ q is the same as p ≤ q which equates in sets to P⊆ Q.
In the example here, since the only two students who have applied for ‘EE’ have also applied for ‘CS’, applying for ‘EE’ implies applying for ‘CS’.
A search for ‘EE’ students who have not applied for ‘CS’ returns false:
apply(SID, _, 'EE', _), \+apply(SID, _, 'CS', _).
This is a clue that the trick to finding if one set is a subset of another, a statement like the one above will return false, so it’s negation will true.
I’m going to turn this into a general query to find all majors in the example database which are subsets to other majors. A quick diversion into logic manipulation first since it’s an interesting application of De Morgan’s law: if we abstract the above Prolog query to ¬(p ∧ ¬q) it can be rewritten the conventional way ¬p ∨ q. The truth table for p ⇒ q is more easily understood as p ≤ q in my opinion.
p ⇒ q is equivalent to ¬p ∨ q, which using De Morgan’s Law can be rewritten ¬(p ∧ ¬q)
A way to find all supersets and their subsets in this example looks like so:
distinct([Superset, Subset],
( apply(_, _, Subset, _), apply(_, _, Superset, _),
\+ (apply(_SID, _, Subset, _), \+apply(_SID, _, Superset, _)),
Subset \== Superset
)).
Equality
Two sets are equal if they are subsets of each other. There aren’t any in this example.