Q: Is there a one-to-one and onto correspondence between integers and rational numbers?
A: Yes! But it’s long and complicated, so our intuition breaks down, and it’s really easy to make a mistake and say that a bijection doesn’t exist.
Q: How would you define reachability without using ^
in Alloy?
A:
sig Node { edges : set Node }
one sig Reach { reach : Node -> Node }
fact defineReach {
//Reach.reach = ^edges
edges in Reach.reach
edges.edges in Reach.reach
edges.edges.edges in Reach.reach
// and so on
}
Q: How can we make this fact finite?
A: Well, since Alloy has bounds, we can only consider a certain a number of steps. So for example, if we have 5 Nodes, then we can cover transitive closure with 4 steps. This is essentially what Alloy does when you say ^edges
Q: What if we don’t want to use this bounds trick?
edges in Reach.reach
edges.(Reach.reach) in Reach.reach
all n1, n2: Node | {
n1->n2 in Reach.reach iff (
n1->n2 in edges or
some n3: Node | {n1->n3 in Reach.reach and n3->n2 in Reach.reach}
)
}
Definition of transitive closure: The smallest set such that the above holds.
How can we fix our predicate so that it can’t add in extra things?
all n1, n2: Node | {
n1->n2 in Reach.reach iff (
n1->n2 in edges or
some n3: Node | {n1->n3 in Reach.reach and n3->n2 in edges and n1 != n3 and n2 != n3}
)
}
This still doesn’t work, it’s just using longer and longer cycles
It turns out that you cannot define transitive closure just using Alloy’s logic
There is a kind of complicated way to define transitive closure under the guarantee that you will only be looking at finite relations. But it uses three or four helper relations, and it gets really time-intensive, so it ends up being better to just use the trick we talked about at the beginning.
Q: What makes a good language?
A:
grep
for regular expressions, but regular expressions are not Turing complete.reach(X,Y) :- edge(X,Y).
reach(X,Y) :- reach(X,Z), edge(Z,Y).
This is basically the same thing we just wrote in Alloy, but it actually works in Datalog.
Datalog starts with edge
, and sticks everything in edge
into reach
. Then, once reach
has things in it, it starts to use the second rule to expand reach
.
Notice that we’re talking about applying rules, not compiling down to SAT or anything like that. This is really a mechanical process to construct these databases.
Q: Why is this working but Alloy doesn’t?
A: Alloy didn’t give us the minimal set. It was just looking for any instance that satisfied the properties. Here, with this construction process, we inherently get the minimal set.