Tag(sp22)
Today we'll work with a fairly simple model, and use it to demonstrate how reachability works in Forge. That is, You'll see how the reachable
predicate is defined (via transitive closure), and get some more practice with sets in Forge.
Consider this small Forge model:
Let's run it, get a reasonably large instance, and try to figure out how to express some goals in the evaluator.
This is just Nim.followers
.
Another application of the field! Nim.followers.followers
.
We can use the reachable
built-in: reachable[Nim, Tim, followers
.
This all seems pretty doable…
Hmm. This seems harder. We don't have a field called following
, and the reachable
built-in takes fields!
Right?
Let's try something else.
We might try reachable[Nim, Tim, (followers-Tim.friends)]
but this will produce an error. Why? Well, because followers
is a field but Tim.friends
is a set.
But is that the real answer? The error complains about "arity": 2 vs 1. Let's type those two expressions into the evaluator and see what we get. For followers
, we get a set of pairs of people. But Tim.friends
is a set of singletons.
Important: The evaluator prints these as parenthesized lists of lists. But don't be fooled! It's really printing a set of lists. The order in which the inner lists print shouldn't matter.
Arity is another word for how wide the elements of a set are. Here, we'd say that followers
has arity 2 and Tim.friends
has arity 1. So Forge is pointing out that taking the set difference of these two makes no sense: you'll never find a singleton in a set of pairs.
When we're talking about sets in this way, we sometimes call them relations. E.g., the followers
field is a binary relation because it has arity 2. We'll call elements of relations tuples. E.g., if Nim
follows Tim
, the tuple (Tim, Nim)
would be present in followers
.
The truth is that reachable
doesn't take "fields"; it takes relations. Specifically, binary relations, which define the steps it can use to connect the two objects.
That's the fact we'll use to solve the 2 problems above.
Now that we know followers
is a binary relation, we can imagine flipping it to get its inverse. How can we do that? Well, there are multiple ways! We could write a set-comprehension:
The order matters here! If p1
is in p2.followers
, then there is an entry in the relation that looks like (p2, p1)
. We could make this more explicit by writing:
Now that we know followers
is a set, this makes sense! The product (->
) operator combines p2
and p1
into a binary tuple, which may (or may not) be in followers
.
Forge provides an operator that does this directly: transpose (~
). So we could write instead:
Which should you use? It's up to you! Regardless, we could now answer this question with:
Everything is a set, so let's build the subset of followers
that doesn't involve anyone in Tim.friends
. We can't write followers-(Tim.friends)
, since that's an arity mismatch. Somehow, we need to remove entries in followers
involving one of Tim's friends and anybody else.
One way is to use the product operator to build cooresponding binary relations. E.g.:
Important: You may notice that we've now used ->
in what seems like 2 different ways. We used it to combine specific people, p1
and p2
above into a single tuple. But now we're using it to combine two sets into a set of tuples. This flexibility is a side effect of sets being the fundamental concept in full Forge:
(Tim)
and (Nim)
is (Tim, Nim)
;((Person1), (Person2))
and ((Person3), (Person4))
is ((Person1, Person3), (Person1, Person4), (Person2, Person3), (Person2, Person4))
.Formally, ->
is the cross-product operation on sets.
You'll see this apparently double-meaning when using in
and =
, too: singletons are single-element sets.
If we wanted to encode reachability, we could start by writing a helper predicate:
But we always have to stop writing somewhere. We could write the union out to 20 steps, and still we wouldn't catch some very long (length 21 or higher) paths. So we need some way to talk about unbounded reachability.
Forge's reachable
built-in does this, but it's just a facade over a new relational operator: transitive closure (^R
).
The transitive closure ^R
of a binary relation R
is the smallest binary relation such that:
(x, y)
is in R
, then (x, y)
is in ^R
; and(x, z)
is in R
and (z, y)
is in ^R
then (x, y)
is in R
.That is, ^R
encodes exactly what we were trying to encode above. The reachable[to, from, f1, ...]
built-in is just syntactic sugar for:
You'll get more practice with this in this week's lab.