The short answer is that it doesn't compose very well, especially when doing relational programming.
Let's say that `people` is a record of some sort, and not a relation. That is, when we extract people, we get records of data which we can manipulate, and not a set of functions which let us relate the records to each other. Now, imagine a logical query which has the following steps:
exists p :Person
guard p.name == "Titus"
exists gp :Person ? isGrandParent(gp, p)
call scan(gp)
return gp, p
How is this actually going to be executed? Well, you can imagine that first, we'll fetch a Person record, then we'll check if the name is "Titus", and then we'll fetch his grandparents, calling `scan` on each one. (No, I don't know why we would scan grandparents. Here in the USA, we actually let them skip the scanners.) Seems easy, right? What if we make a tiny swap... exists p :Person
exists gp :Person ? isGrandParent(gp, p)
call scan(gp)
guard p.name == "Titus"
return gp, p
Crap, this isn't the same program. We'll scan every grandparent, even ones without grandkids named Titus.Relational operators are supposed to commute, but these ones don't commute! What happened? Well, accessing a runtime-computed non-relational fact, like the name of a person here, is an impure operation. It doesn't compose.
So! What's the right answer? We have to express the person<->name connection as a relation rather than a record:
exists p :Person ? personHasName(p, "Titus")
exists gp :Person ? isGrandParent(gp, p)
call somePred(gp)
return gp, p
Byrd's thesis glosses over this point and simply says that we should already have internalized the impurity of `project` or other projection operators.Edit: I fixed some typos. Also, this paper [0] on my paperstack has Friedman as a co-author and purports to solve this problem in a generic way by adding pure symbolic constraints to miniKanren.