Add more text to draft.
This commit is contained in:
parent
6a6f25547e
commit
b3ff2fe135
|
@ -1 +1 @@
|
||||||
Subproject commit 2b69cbd391398ed21ccf3b2b06d62dc46207fe85
|
Subproject commit 7a8503c3fe1aa7e624e4d8672aa9b56d24b4ba82
|
|
@ -735,6 +735,139 @@ A program can always take a step, and each time it does,
|
||||||
the set of valid program counters decreases in size. Eventually,
|
the set of valid program counters decreases in size. Eventually,
|
||||||
this set will become empty, so if nothing else, our program will
|
this set will become empty, so if nothing else, our program will
|
||||||
eventually terminate in an "error" state. Thus, it will stop
|
eventually terminate in an "error" state. Thus, it will stop
|
||||||
running no matter what.
|
running no matter what.
|
||||||
|
|
||||||
The problem is: how do we express that?
|
This seems like a task for induction, in this case on the size
|
||||||
|
of the valid set. In particular, strong mathematical induction
|
||||||
|
{{< sidenote "right" "strong-induction-note" "seem to work best." >}}
|
||||||
|
Why strong induction? If we remove a single element from a set,
|
||||||
|
its size should decrease strictly by 1. Thus, why would we need
|
||||||
|
to care about sets of <em>all</em> sizes less than the current
|
||||||
|
set's size?<br>
|
||||||
|
<br>
|
||||||
|
Unfortunately, we're not working with purely mathematical sets.
|
||||||
|
Coq's default facility for sets is simply a layer on top
|
||||||
|
of good old lists, and makes no effort to be "correct by construction".
|
||||||
|
It is thus perfectly possible to have a "set" which inlcudes an element
|
||||||
|
twice. Depending on the implementation of <code>set_remove</code>,
|
||||||
|
we may end up removing the repeated element multiple times, thereby
|
||||||
|
shrinking the length of our list by more than 1. I'd rather
|
||||||
|
not worry about implementation details like that.
|
||||||
|
{{< /sidenote >}}
|
||||||
|
Someone on StackOverflow [implemented this](https://stackoverflow.com/questions/45872719/how-to-do-induction-on-the-length-of-a-list-in-coq),
|
||||||
|
so I'll just use it. The Coq theorem corresonding to strong induction
|
||||||
|
on the length of a list is as follows:
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 205 207 >}}
|
||||||
|
|
||||||
|
It reads,
|
||||||
|
|
||||||
|
> If for some list `l`, the property `P` holding for all lists
|
||||||
|
shorter than `l` means that it also holds for `l` itself, then
|
||||||
|
`P` holds for all lists.
|
||||||
|
|
||||||
|
This is perhaps not particularly elucidating. We can alternatively
|
||||||
|
think of this as trying to prove some property for all lists `l`.
|
||||||
|
We start with all empty lists. Here, we have nothing else to rely
|
||||||
|
on; there are no lists shorter than the empty list, and our property
|
||||||
|
must hold for all empty lists. Then, we move on to proving
|
||||||
|
the property for all lists of length 1, already knowing that it holds
|
||||||
|
for all empty lists. Once we're done there, we move on to proving
|
||||||
|
that `P` holds for all lists of length 2, now knowing that it holds
|
||||||
|
for all empty lists _and_ all lists of length 1. We continue
|
||||||
|
doing this, eventually covering lists of any length.
|
||||||
|
|
||||||
|
Before proving termination, there's one last thing we have to
|
||||||
|
take care off. Coq's standard library does not come with
|
||||||
|
a proof that removing an element from a set makes it smaller;
|
||||||
|
we have to provide it ourselves. Here's the claim encoded
|
||||||
|
in Coq:
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 217 219 >}}
|
||||||
|
|
||||||
|
This reads, "if a set `s` contains a finite natural
|
||||||
|
number `f`, removing `f` from `s` reduces the set's size".
|
||||||
|
The details of the proof are not particularly interesting,
|
||||||
|
and I hope that you understand intuitively why this is true.
|
||||||
|
Finally, we make our termination claim.
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 230 231 >}}
|
||||||
|
|
||||||
|
It's quite a strong claim - given _any_ program counter,
|
||||||
|
set of valid addresses, and accumulator, a valid input program
|
||||||
|
will terminate. Let's take a look at the proof.
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 232 234 >}}
|
||||||
|
|
||||||
|
We use `intros` again. However, it brings in variables
|
||||||
|
in order, and we really only care about the _second_ variable.
|
||||||
|
We thus `intros` the first two, and then "put back" the first
|
||||||
|
one using `generalize dependent`. Then, we proceed by
|
||||||
|
induction on length, as seen above.
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 235 236>}}
|
||||||
|
|
||||||
|
Now we're in the "inductive step". Our inductive hypothesis
|
||||||
|
is that any set of valid addresses smaller than the current one will
|
||||||
|
guarantee that the program will terminate. We must show
|
||||||
|
that using our set, too, will guarantee termination. We already
|
||||||
|
know that a valid input, given a state, can have one of three
|
||||||
|
possible outcomes: "ok" termination, "failed" termination,
|
||||||
|
or a "step". We use `destruct` to take a look at each of these
|
||||||
|
in turn. The first two cases ("ok" termination and "failed" termination)
|
||||||
|
are fairly trivial:
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 237 240 >}}
|
||||||
|
|
||||||
|
We basically connect the dots between the premises (in a form like `done`)
|
||||||
|
and the corresponding inference rule (`run_noswap_done`). The more
|
||||||
|
interesting case is when we can take a step.
|
||||||
|
|
||||||
|
{{< codelines "Coq" "aoc-2020/day8.v" 241 253 >}}
|
||||||
|
|
||||||
|
Since we know we can take a step, we know that we'll be removing
|
||||||
|
the current program counter from the set of valid addresses. This
|
||||||
|
set must currently contain the present program counter (since otherwise
|
||||||
|
we'd have "failed"), and thus will shrink when we remove it. This,
|
||||||
|
in turn, lets us use the inductive hypothesis: it tells us that no matter the
|
||||||
|
program counter or accumulator, if we start with this new "shrunk"
|
||||||
|
set, we will terminate in some state. Coq's constructive
|
||||||
|
nature helps us here: it doesn't just tells us that there is some state
|
||||||
|
in which we terminate - it gives us that state! We use `edestruct` to get
|
||||||
|
a handle on this final state, which Coq automatically names `x`. At this
|
||||||
|
time Coq still isn't convinced that our new set is smaller, so we invoke
|
||||||
|
our earlier `set_remove_length` theorem to placate it.
|
||||||
|
|
||||||
|
We now have all the pieces: we know that we can take a step, removing
|
||||||
|
the current program counter from our current set. We also know that
|
||||||
|
with that newly shrunken set, we'll terminate in some final state `x`.
|
||||||
|
Thus, all that's left to say is to apply our "step" rule. It asks
|
||||||
|
us for three things:
|
||||||
|
|
||||||
|
1. That the current program counter is in the set. We've long since
|
||||||
|
established this, and `auto` takes care of that.
|
||||||
|
2. That a step is possible. We've already established this, too,
|
||||||
|
since we're in the "can take a step" case. We apply `Hst`,
|
||||||
|
the hypothesis that confirms that we can, indeed, step.
|
||||||
|
3. That we terminate after this. The `x` we got
|
||||||
|
from our induction hypothesis came with a proof that
|
||||||
|
running with the "next" program counter and accumulator
|
||||||
|
will result in termination. We apply this proof, automatically
|
||||||
|
named `H0` by Coq.
|
||||||
|
|
||||||
|
And that's it! We've proved that a program terminates no matter what.
|
||||||
|
This has also (almost!) given us a solution to part 1. Consider the case
|
||||||
|
in which we start with program counter 0, accumulator 0, and the "full"
|
||||||
|
set of allowed program counters. Since our proof works for _all_ configurations,
|
||||||
|
it will also work for this one. Furthermore, since Coq proofs are constructive,
|
||||||
|
this proof will __return to us the final program counter and accumulator!__
|
||||||
|
This is precisely what we'd need to solve part 1.
|
||||||
|
|
||||||
|
But wait, almost? What's missing? We're missing a few implementation details:
|
||||||
|
* We've not provided a concrete impelmentation of integers.
|
||||||
|
* We assumed (reasonably, I would say) that it's possible to convert a natural
|
||||||
|
number to an integer.
|
||||||
|
* We also assumed (still reasonably) that we can try convert an integer
|
||||||
|
back to a finite natural number, failing if it's too small or too large.
|
||||||
|
|
||||||
|
{{< todo >}}Finish up{{< /todo >}}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user