Want to write a spec to allow multiple trees
Q: Why am I defining this such that trees can "re-use" a Node
? E.g. such that each Node
can be in multiple Tree
s.
A: This is much easier with Alloy so that you can simply have fewer things in your Alloy instance.
Q: Does run Everything {} just mean the same thing as run{} ?
A: This is just the name Tim has given his run statement to make it easier to keep up.
When we run our instance, it doesn't quite work yet... We haven't added our BSTree
constraints.
We have to modify our BSTree
to take in a Tree
because it is no longer globally applicable.
We have an instance, but it's not exactly what we expect. When we project over Tree
, we just get a bunch of unrelated nodes.
This is because we're actually seeing the empty instance! However, looking at the other Tree
gives us something that looks like a binary tree!
Let's start with add
ing a Node
. As you're adding, what do you want to hold true?
Separate out the things that we are defining, and what we think this implies in our universe. E.g. do not define something like prev.tree is BSTree => post.tree is BSTree
. This is actually a property that you want to hold true about your model, not a property you want to explicitly bake into your logic.
Tim is saving this as a module
multitrees so that he can partition this model into discrete pieces of code.
We define a sig Descent
. What does it mean to be a Descent
, algorithmically something that follows a path looking for a particular value?
>
or <
So, we define in our descent
all idx: path.inds | {
path[idx].num = val implies idx = path.lastIdx
path[idx] in Empty implies idx = path.lastIdx
path[idx].num > val implies path[add[idx, 1]] = t.lefts[path[idx]]
path[idx].num < val implies path[add[idx,1]] = t.rights[path[idx]]
}
Some explanations for the above code:
If the current number of the current node > the value we're looking for path[idx].num > val
In the tree that we are currently searching, the left child of our current node path[add[idx, 1]] = t.lefts[path[idx]]
This is how we have encoded our algorithm into alloy without specifying properties. We haven't said that the other Node
s must hold true or they must still be a BSTree
. We've merely specified the algorithm to look for a particular value in our nodes.
We get Descent
s, but they are not particularly interesting... so we specify #(d.path.elems) > 2
The spec is broken! The Descent
returns the Empty
Node as a valid found Node
because there's nothing forcing the Empty
Node to be different or non-returnable.
By fixing this, Tim found an instance that works in Alloy! Does this mean it's right? Not necessarily...
To test, we define a pred:
pred uhoh {
some d:Descent | {
testDescentForFound[d]
some (d.path.elems & Empty)
}
}
This is the kind of thing you want to do if there is something about your spec that looks suspicious and you want to check.