Flesh out the Lattices post some more
Signed-off-by: Danila Fedorin <danila.fedorin@gmail.com>
This commit is contained in:
parent
c036041339
commit
60d3b3025a
|
@ -335,7 +335,8 @@ In Agda, we can therefore write a lattice as follows:
|
|||
|
||||
{{< codelines "Agda" "agda-spa/Lattice.agda" 153 163 >}}
|
||||
|
||||
### Concrete Example: Natural Numbers
|
||||
### Concrete Examples
|
||||
#### Natural Numbers
|
||||
|
||||
Since we've been talking about `min` and `max` as motivators for properties
|
||||
of \((\sqcap)\) and \((\sqcup)\), it might not be all that surprising
|
||||
|
@ -351,3 +352,141 @@ there's no reason not to use definitional equality `≡` for our equivalence rel
|
|||
The definition for the lattice instance itself is pretty similar; I'll omit
|
||||
it here to avoid taking up a lot of vertical space, but you can find it
|
||||
on lines 47 through 83 of [my `Lattice.Nat` module]({{< codeurl "agda-spa/Lattice/Nat.agda" >}}).
|
||||
|
||||
#### The "Above-Below" Lattice
|
||||
It's not too hard to implement our sign lattice in Agda. However, we can
|
||||
do it in a somewhat general way. As it turns out, extending an existing set,
|
||||
such as \(\{+, -, 0\}\), with a "bottom" and "top" element (to be used
|
||||
when taking the least upper bound and greatest lower bound) is quite common
|
||||
and useful. For instance, if we were to do constant propagation (simplifying
|
||||
`7+4` to `11`), we would probably do something similar, using the set
|
||||
of integers \(\mathbb{Z}\) instead of the plus-zero-minus set.
|
||||
|
||||
The general definition is as follows. Take some original set \(S\) (like our 3-element
|
||||
set of signs), and extend it with new "top" and "bottom" elements (\(\top\) and
|
||||
\(\bot\)). Then, define \((\sqcup)\) as follows:
|
||||
|
||||
{{< latex >}}
|
||||
x_1 \sqcup x_2 =
|
||||
\begin{cases}
|
||||
\top & x_1 = \top\ \text{or}\ x_2 = \top \\
|
||||
\top & x_1, x_2 \in S, x_1 \neq x_2 \\
|
||||
x_1 = x_2 & x_1, x_2 \in S, x_1 = x_2 \\
|
||||
x_1 & x_2 = \bot \\
|
||||
x_2 & x_1 = \bot
|
||||
\end{cases}
|
||||
{{< /latex >}}
|
||||
|
||||
In other words, \(\top\) overrules anything that it's combined with. In
|
||||
math terms, it's the __absorbing element__ of the lattice. On the other hand,
|
||||
\(\bot\) gets overruled by anything it's combined with. In math terms, that's
|
||||
an __identity element__. Finally, when combining two elements that _aren't_
|
||||
\(\top\) or \(\bot\) (which would otherwise be covered by the prior sentences),
|
||||
combining an element with itself leaves it unchanged (upholding idempotence),
|
||||
while combining two unequal element results in \(\top\). That last part
|
||||
matches the way we defined "least upper bound" earlier.
|
||||
|
||||
The intuition is as follows: the \((\sqcup)\) operator is like an "or". Then,
|
||||
"anything or positive" means "anything"; same with "anything or negative", etc.
|
||||
On the other hand, "impossible or positive" means positive, since one of those
|
||||
cases will never happen. Finally, in the absense of additional elements, the
|
||||
most we can say about "positive or negative" is "any sign"; of course,
|
||||
"positive or positive" is the same as "positive".
|
||||
|
||||
|
||||
The "greatest lower bound" operator is defined by effectively swapping top
|
||||
and bottom.
|
||||
|
||||
{{< latex >}}
|
||||
x_1 \sqcup x_2 =
|
||||
\begin{cases}
|
||||
\bot & x_1 = \bot\ \text{or}\ x_2 = \bot \\
|
||||
\bot & x_1, x_2 \in S, x_1 \neq x_2 \\
|
||||
x_1 = x_2 & x_1, x_2 \in S, x_1 = x_2 \\
|
||||
x_1 & x_2 = \top \\
|
||||
x_2 & x_1 = \top
|
||||
\end{cases}
|
||||
{{< /latex >}}
|
||||
|
||||
For this operator, \(\bot\) is the absorbing element, and \(\top\) is the
|
||||
identity element. The intuition here is not too different: if
|
||||
\((\sqcap)\) is like an "and", then "impossible and positive" can't happen;
|
||||
same with "impossible and negative", and so on. On the other hand,
|
||||
"anything and positive" clearly means positive. Finally, "negative and positive"
|
||||
can't happen (again, there is no number that's both positive and negative),
|
||||
and "positive and positive" is just "positive".
|
||||
|
||||
What properties of the underlying set did we use to get this to work? The
|
||||
only thing we needed is to be able to check and see if two elements are
|
||||
equal or not; this is called _decidable equality_. Since that's the only
|
||||
thing we used, this means that we can define an "above/below" lattice like this
|
||||
for any type for which we can check if two elements are equal. In Agda, I encoded
|
||||
this using a parameterized module:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Lattice/AboveBelow.agda" 5 8 >}}
|
||||
|
||||
From there, I defined the actual data type as follows:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Lattice/AboveBelow.agda" 23 26 >}}
|
||||
|
||||
From there, I defined the \((\sqcup)\) and \((\sqcap)\) operations almost
|
||||
exactly to the mathematical equation above (the cases were re-ordered to
|
||||
improve Agda's reduction behavior). Here's the former:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Lattice/AboveBelow.agda" 86 93 >}}
|
||||
|
||||
And here's the latter:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Lattice/AboveBelow.agda" 181 188 >}}
|
||||
|
||||
The proofs of the lattice properties are straightforward and proceed
|
||||
by simple case analysis. Unfortunately, Agda doesn't quite seem to
|
||||
evaluate the binary operator in every context that I would expect it to,
|
||||
which has led me to define some helper lemmas such as the following:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Lattice/AboveBelow.agda" 95 96 >}}
|
||||
|
||||
As a sample, here's a proof of commutativity of \((\sqcup)\):
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Lattice/AboveBelow.agda" 158 165 >}}
|
||||
|
||||
The details of the rest of the proofs can be found in the
|
||||
[`AboveBelow.agda` file]({{< codeurl "agda-spa/Lattice/AboveBelow.agda" >}}).
|
||||
|
||||
To recover the sign lattice we've been talking about all along, it's sufficient
|
||||
to define a sign data type:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Analysis/Sign.agda" 19 22 >}}
|
||||
|
||||
Then, prove decidable equality on it (effecitly defining a comparison function),
|
||||
and instantiate the `AboveBelow` module:
|
||||
|
||||
{{< codelines "Agda" "agda-spa/Analysis/Sign.agda" 34 47 >}}
|
||||
|
||||
### Making Lattices out of Lattices
|
||||
|
||||
Natural numbers and signs alone are cool enough, but they will not be sufficient
|
||||
to write program analyzers. That's because when we're writing an analyzer,
|
||||
we don't just care about one variable: we care about all of them! An
|
||||
initial guess might be to say that when analyzing a program, we really need
|
||||
_several_ signs: one for each variable. This might be reminiscent of a
|
||||
[map](https://en.wikipedia.org/wiki/Associative_array). So, when we compare
|
||||
specificity, we'll really be comparing the specificity of maps. Even that,
|
||||
though, is not enough. The reason is that variables might have different
|
||||
signs at different points in the program! A single map would not be able to
|
||||
capture that sort of nuance, so what we really need is a map associating
|
||||
states with another map, which in turn associates variables with their signs.
|
||||
|
||||
Mathematically, we might write this as:
|
||||
|
||||
{{< latex >}}
|
||||
\text{Info} \triangleq \text{ProgramStates} \to (\text{Variables} \to \text{Sign})
|
||||
{{< /latex >}}
|
||||
|
||||
That's a big step up in complexity. We now have a doubly-nested map structure
|
||||
instead of just a sign. and we need to compare such maps in order to gaugage
|
||||
their specificity and advance our analyses. But where do we even start with
|
||||
maps, and how do we define the \((\sqcup)\) and \((\sqcap)\) operations?
|
||||
|
||||
The solution turns out to be to define ways in which simpler lattices
|
||||
(like our sign) can be combined and transformed to define more complex lattices.
|
||||
|
|
Loading…
Reference in New Issue
Block a user