The first goal in the conjunction instantiates the x
, the second instantiates the y
, and run (x y)
says to report both.
Both x
and y
get instantiated because x
is different from y
, so they are independent from each other.
This can be modeled under the solutions streams' combination paradigm as in this answer of mine (equivalent to the book's model sans the logical variables), as this pseudocode:
(1.) s
is a solution, a substitution, a record of (name,value)
pairs.
It is extended by unification:
(var ≡ val) s = [[(x,v)] ++: s] // IF (x,_) NOT IN s , OR
| = [s] // IF (x,u) IN s AND u=v , OR
| = [] // OTHERWISE.
(all the operators like ++:
("append" a.k.a. "mplus"), ||:
("or" a.k.a. "disj2"), >>:
("push-through" a.k.a. "bind"), &&:
("and" a.k.a. "conj2"), etc. are defined in the linked answer. The syntax is a free-licensed "curried application by juxtaposition", similar to Haskell's).
A substitution represents our knowledge so far. The unification (x ≡ TEA)
, say, is a goal -- a procedure which, given a substitution, produces a stream of new substitutions (here, one or none), augmented with the new knowledge that x ≡ TEA
(we use all-caps names as auto-quoted symbols).
Augmenting a substitution involves possible rejection of irreconcilable values.
The result is a list containing either one valid, possibly extended substitution, or an empty list containing none, meaning (x,v)
was incompatible with s
. This expresses the notion that a logical variable's value, once set, can not change (except through backtracking).
(2.) A disjunction involves application of the goals, separately, and passing along all the resulting valid solutions (substitutions) together:
teacup° x = x ≡ TEA ||: x ≡ CUP
-- thus,
teacup° x s = (x ≡ TEA ||: x ≡ CUP) s
= (x ≡ TEA) s ++: (x ≡ CUP) s
(3.) A conjunction passes each solution of the first goal through the second:
(teacup° x &&: teacup° y) [] -- the initial substitution is empty
= teacup° x [] >>: teacup° y
= ( (x ≡ TEA ||: x ≡ CUP) [] ) >>: teacup° y
= ( (x ≡ TEA) [] ++: (x ≡ CUP) [] ) >>: teacup° y
= ( [[(x,TEA)]] ++: [[(x,CUP)]] ) >>: teacup° y
= [[(x,TEA)] , [(x,CUP)]] >>: teacup° y
= teacup° y [(x,TEA)] ++: teacup° y [(x,CUP)] ++: []
= (y ≡ TEA ||: y ≡ CUP) [(x,TEA)] ++:
(y ≡ TEA ||: y ≡ CUP) [(x,CUP)]
= (y ≡ TEA)[(x,TEA)] ++: (y ≡ CUP) [(x,TEA)] ++:
(y ≡ TEA) [(x,CUP)] ++: (y ≡ CUP) [(x,CUP)]
= [[(y,TEA),(x,TEA)]] ++: [[(y,CUP),(x,TEA)]] ++:
[[(y,TEA),(x,CUP)]] ++: [[(y,CUP),(x,CUP)]]
= [[(y,TEA),(x,TEA)] , [(y,CUP),(x,TEA)] ,
[(y,TEA),(x,CUP)] , [(y,CUP),(x,CUP)]]
All in all, four solutions, four substitutions satisfying the goal teacup° x &&: teacup° y
, were produced.
A conjunction &&:
of two goals will, for each of the solutions produced by the first goal, collect all the solutions produced by the second which are conformant with the solution received by the second goal. Here, since x
and y
are two separate variables, every new solution does not collide with the old, in other words, none of the solutions are rejected.
(4.) If we'd used the same variable twice, their interaction would mean that we must reject irreconcilable substitutions:
(teacup° x &&: teacup° x) [] -- the initial substitution is empty
= teacup° x [] >>: teacup° x
= ( (x ≡ TEA ||: x ≡ CUP) [] ) >>: teacup° x
= ( (x ≡ TEA) [] ++: (x ≡ CUP) [] ) >>: teacup° x
= ( [[(x,TEA)]] ++: [[(x,CUP)]] ) >>: teacup° x
= [[(x,TEA)] , [(x,CUP)]] >>: teacup° x
= teacup° x [(x,TEA)] ++: teacup° x [(x,CUP)] ++: []
= (x ≡ TEA ||: x ≡ CUP) [(x,TEA)] ++:
(x ≡ TEA ||: x ≡ CUP) [(x,CUP)]
= (x ≡ TEA)[(x,TEA)] ++: (x ≡ CUP) [(x,TEA)] ++:
(x ≡ TEA) [(x,CUP)] ++: (x ≡ CUP) [(x,CUP)]
= [[(x,TEA)]] ++: [] ++: [] ++: [[(x,CUP)]]
= [[(x,TEA)] , [(x,CUP)]]
Here, two solutions are produced because the same logical variable can not hold two different values at once, so e.g. (x ≡ CUP) [(x,TEA)]
resolves as []
, the empty list containing no solutions, thus rejecting the attempted assignment (x,CUP)
when (x,TEA)
is already present.
(5.) (run* (x y) (teacup° x) (teacup° y))
is more or less like
[[]] >>: (teacup° x &&: teacup° y) >>: report [x, y]
==
(teacup° x &&: teacup° y) [] >>: report [x, y]
==
[[(y,TEA),(x,TEA)], [(y,CUP),(x,TEA)],
[(y,TEA),(x,CUP)], [(y,CUP),(x,CUP)]] >>: report [x, y]
==
report [x, y] [(y,TEA),(x,TEA)] ++:
report [x, y] [(y,CUP),(x,TEA)] ++:
report [x, y] [(y,TEA),(x,CUP)] ++:
report [x, y] [(y,CUP),(x,CUP)]
==
[ (TEA,TEA), (TEA,CUP), (CUP,TEA), (CUP,CUP) ]
for a suitably defined report <vars>
goal.
(6.) And run* (x) (teacup° x) (teacup° x))
is more or less like
[[]] >>: (teacup° x &&: teacup° x) >>: report [x]
==
(teacup° x &&: teacup° x) [] >>: report [x]
==
[[(x,TEA)], [(x,CUP)]] >>: report [x]
==
report [x] [(x,TEA)] ++: report [x] [(x,CUP)]
==
[ TEA, CUP ]