-
Notifications
You must be signed in to change notification settings - Fork 63
Expand file tree
/
Copy pathTutorial.lean
More file actions
975 lines (816 loc) · 41.4 KB
/
Copy pathTutorial.lean
File metadata and controls
975 lines (816 loc) · 41.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
/-
Copyright (c) 2025 Lean FRO LLC. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Sebastian Graf
-/
import VersoManual
import Manual.Meta
import Manual.Papers
import Std.Tactic.Do
open Verso.Genre Manual
open Verso.Genre.Manual.InlineLean
open Verso.Code.External (lit)
set_option pp.rawOnError true
set_option verso.docstring.allowMissing true
set_option linter.unusedVariables false
set_option linter.typography.quotes true
set_option linter.typography.dashes true
set_option mvcgen.warning false
#doc (Manual) "Tutorial: Verifying Imperative Programs Using `mvcgen`" =>
%%%
tag := "mvcgen-tactic-tutorial"
htmlSplit := .never
%%%
This section is a tutorial that introduces the most important concepts of {tactic}`mvcgen` top-down.
Recall that you need to import {module}`Std.Tactic.Do` and open {namespace}`Std.Do` to run these examples:
```imports
import Std.Tactic.Do
```
```lean
open Std.Do
```
# Preconditions and Postconditions
One style in which program specifications can be written is to provide a {tech}_precondition_ $`P`, which the caller of a program $`\mathit{prog}` is expected to ensure, and a {tech}_postcondition_ $`Q`, which the $`\mathit{prog}` is expected to ensure.
The program $`\mathit{prog}` satisfies the specification if running it when the precondition $`P` holds always results in the postcondition $`Q` holding.
In general, many different preconditions might suffice for a program to ensure the postcondition.
After all, new preconditions can be generated by replacing a precondition $`P_1` with $`P_1 \wedge P_2`.
The {tech}_weakest precondition_ $`\textbf{wp}⟦\mathit{prog}⟧(Q)` of a program $`\mathit{prog}` and postcondition $`Q` is a precondition for which $`\mathit{prog}` ensures the postcondition $`Q` and is implied by all other such preconditions.
One way to prove something about the result of a program is to find the weakest precondition that guarantees the desired result, and then to show that this weakest precondition is simply true.
This means that the postcondition holds no matter what.
# Loops and Invariants
:::leanFirst
As a first example of {tactic}`mvcgen`, the function {name}`mySum` computes the sum of an array using {ref "let-mut"}[local mutable state] and a {keywordOf Lean.Parser.Term.doFor}`for` loop:
```lean
def mySum (l : Array Nat) : Nat := Id.run do
let mut out := 0
for i in l do
out := out + i
return out
```
:::
If {name}`mySum` is correct, then it is equal to {name}`Array.sum`.
In {name}`mySum`, the use of {keywordOf Lean.Parser.Term.do}`do` is an internal implementation detail—the function's signature makes no mention of any monad.
Thus, the proof first manipulates the goal into a form that is amenable to the use of {tactic}`mvcgen`, using the lemma {name}`Id.of_wp_run_eq`.
This lemma states that facts about the result of running a computation in the {name}`Id` monad that terminates normally (`Id` computations never throw exceptions) can be proved by showing that the {tech}[weakest precondition] that ensures the desired result is true.
Next, the proof uses {tactic}`mvcgen` to replace the formulation in terms of weakest preconditions with a set of {tech}[verification conditions].
While {tactic}`mvcgen` is mostly automatic, it does require an invariant for the loop.
A {tech}_loop invariant_ is a statement that is both assumed and guaranteed by the body of the loop; if it is true when the loop begins, then it will be true when the loop terminates.
```lean
theorem mySum_correct (l : Array Nat) : mySum l = l.sum := by
-- Focus on the part of the program with the `do` block (`Id.run ...`)
generalize h : mySum l = x
apply Id.of_wp_run_eq h
-- Break down into verification conditions
mvcgen
-- Specify the invariant which should hold throughout the loop
-- * `out` refers to the current value of the `let mut` variable
-- * `xs` is a `List.Cursor`, which is a data structure representing
-- a list that is split into `xs.prefix` and `xs.suffix`.
-- It tracks how far into the loop we have gotten.
-- Our invariant is that `out` holds the sum of the prefix.
-- The notation ⌜p⌝ embeds a `p : Prop` into the assertion language.
case inv1 => exact ⇓⟨xs, out⟩ => ⌜xs.prefix.sum = out⌝
-- After specifying the invariant, we can further simplify our goals
-- by "leaving the proof mode". `mleave` is just
-- `simp only [...] at *` with a stable simp subset.
all_goals mleave
-- Prove that our invariant is preserved at each step of the loop
case vc1 ih =>
-- The goal here mentions `pref`, which binds the `prefix` field of
-- the cursor passed to the invariant. Unpacking the
-- (dependently-typed) cursor makes it easier for `grind`.
grind
-- Prove that the invariant is true at the start
case vc2 =>
grind
-- Prove that the invariant at the end of the loop implies the
-- property we wanted
case vc3 h =>
grind
```
:::paragraph
Note that the case labels are actually unique prefixes of the full case labels.
Whenever referring to cases, only this prefix should be used; the suffix is merely a hint to the user of where that particular {tech}[VC] came from.
For example:
* `vc1.step` conveys that this {tech}[VC] proves the inductive step for the loop
* `vc2.a.pre` is meant to prove that the hypotheses of a goal imply the precondition of a specification (of {name}`forIn`).
* `vc3.a.post.success` is meant to prove that the postcondition of a specification (of {name}`forIn`) implies the desired property.
:::
:::paragraph
After specifying the loop invariant, the proof can be shortened to just {keyword}`all_goals mleave; grind` (where {tactic}`mleave` leaves the stateful proof mode, cleaning up the proof state).
```lean
theorem mySum_correct_short (l : Array Nat) : mySum l = l.sum := by
generalize h : mySum l = x
apply Id.of_wp_run_eq h
mvcgen
case inv1 => exact ⇓⟨xs, out⟩ => ⌜xs.prefix.sum = out⌝
all_goals mleave; grind
```
This pattern is so common that {tactic}`mvcgen` comes with special syntax for it:
```lean
theorem mySum_correct_shorter (l : Array Nat) : mySum l = l.sum := by
generalize h : mySum l = x
apply Id.of_wp_run_eq h
mvcgen
invariants
· ⇓⟨xs, out⟩ => ⌜xs.prefix.sum = out⌝
with grind
```
The {keyword}`mvcgen invariants `{lit}`...`{keyword}` with `{lit}`...` is an abbreviation for the
tactic sequence {keyword}`mvcgen; case`{lit}` inv1 => ...`{keyword}`; all_goals mleave; grind`
above. It is the form that we will be using from now on.
:::
:::paragraph
It is helpful to compare the proof of {name}`mySum_correct_shorter` to a traditional correctness proof:
```lean
theorem mySum_correct_vanilla (l : Array Nat) : mySum l = l.sum := by
-- Turn the array into a list
cases l with | mk l =>
-- Unfold `mySum` and rewrite `forIn` to `foldl`
simp [mySum]
-- Generalize the inductive hypothesis
suffices h : ∀ out, List.foldl (· + ·) out l = out + l.sum by simp [h]
-- Grind away
induction l with grind
```
:::
:::paragraph
This proof is similarly succinct as the proof in {name}`mySum_correct_shorter` that uses {tactic}`mvcgen`.
However, the traditional approach relies on important properties of the program:
* The {keywordOf Lean.Parser.Term.doFor}`for` loop does not {keywordOf Lean.Parser.Term.doBreak}`break` or {keywordOf Lean.Parser.Term.doReturn}`return` early. Otherwise, the {name}`forIn` could not be rewritten to a {name Array.foldl}`foldl`.
* The loop body {lean (type := "Nat → Nat → Nat")}`(· + ·)` is small enough to be repeated in the proof.
* The loop body does not carry out any effects in the underlying monad (that is, the only effects are those introduced by {keywordOf Lean.Parser.Term.do}`do`-notation).
The {name}`Id` monad has no effects, so all of its comptutations are pure.
While {name}`forIn` could still be rewritten to a {name Array.foldlM}`foldlM`, reasoning about the monadic loop body can be tough for {tactic}`grind`.
In the following sections, we will go through several examples to learn about {tactic}`mvcgen` and its support library, and also see where traditional proofs become difficult.
This is usually caused by:
* {keywordOf Lean.Parser.Term.do}`do` blocks using control flow constructs such as {keywordOf Lean.Parser.Term.doFor}`for` loops, {keywordOf Lean.Parser.Term.doBreak}`break`s and early {keywordOf Lean.Parser.Term.doReturn}`return`.
* The use of effects in non-{name}`Id` monads, which affects the implicit monadic context (state, exceptions) in ways that need to be reflected in loop invariants.
{tactic}`mvcgen` scales to these challenges with reasonable effort.
:::
# Control Flow
:::leanFirst
Let us consider another example that combines {keywordOf Lean.Parser.Term.doFor}`for` loops with an early return.
{name}`List.Nodup` is a predicate that asserts that a given list does not contain any duplicates.
The function {name}`nodup` below decides this predicate:
```lean
def nodup (l : List Int) : Bool := Id.run do
let mut seen : Std.HashSet Int := ∅
for x in l do
if x ∈ seen then
return false
seen := seen.insert x
return true
```
:::
:::paragraph
This function is correct if it returns {name}`true` for every list that satisfies {name}`List.Nodup` and {name}`false` for every list that does not.
Just as it was in {name}`mySum`, the use of {keywordOf Lean.Parser.Term.do}`do`-notation and the {name}`Id` monad is an internal implementation detail of {name}`nodup`.
Thus, the proof begins by using {name}`Id.of_wp_run_eq` to make the proof state amenable to {tactic}`mvcgen`:
```lean
theorem nodup_correct (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen
invariants
· Invariant.withEarlyReturn
(onReturn := fun ret seen => ⌜ret = false ∧ ¬l.Nodup⌝)
(onContinue := fun xs seen =>
⌜(∀ x, x ∈ seen ↔ x ∈ xs.prefix) ∧ xs.prefix.Nodup⌝)
with grind
```
:::
:::paragraph
```lean -show
section
variable {l : List Int} {ret : Bool} {seen : Std.HashSet Int} {xs : l.Cursor}
axiom onReturn : Bool → Std.HashSet Int → SPred PostShape.pure.args
axiom onContinue : l.Cursor → Std.HashSet Int → SPred PostShape.pure.args
axiom onExcept : ExceptConds PostShape.pure
```
The proof has the same succinct structure as for the initial {name}`mySum` example, because we again offload all proofs to {tactic}`grind` and its existing automation around {name}`List.Nodup`.
Therefore, the only difference is in the {tech}[loop invariant].
Since our loop has an {ref "early-return"}[early return], we construct the invariant using the helper function {lean}`Invariant.withEarlyReturn`.
This function allows us to specify the invariant in three parts:
* {lean}`onReturn ret seen` holds after the loop was left through an early return with value {lean}`ret`.
In case of {name}`nodup`, the only value that is ever returned is {name}`false`, in which case {name}`nodup` has decided there _is_ a duplicate in the list.
* {lean}`onContinue xs seen` is the regular induction step that proves the invariant is preserved each loop iteration.
The iteration state is captured by the cursor {lean}`xs`.
The given example asserts that the set {lean}`seen` contains all the elements of previous loop iterations and asserts that there were no duplicates so far.
* {lean}`onExcept` must hold when the loop throws an exception.
There are no exceptions in {lean}`Id`, so we leave it unspecified to use the default.
(Exceptions will be discussed at a later point.)
```lean -show
end
```
:::
:::paragraph
Note that the form `mvcgen invariants?` will suggest an initial invariant using {name}`Invariant.withEarlyReturn`, so there is no need to memorize the exact syntax for specifying invariants:
```lean (name := invariants?)
example (l : List Int) : nodup l ↔ l.Nodup := by
generalize h : nodup l = r
apply Id.of_wp_run_eq h
mvcgen invariants? <;> sorry
```
The tactic suggests a starting invariant.
This starting point will not allow the proof to succeed—after all, if the invariant can be inferred by the system, then there's no need to make the user specify it—but it does provide a reminder of the correct syntax to use for assertions in the current monad:
```leanOutput invariants?
Try this:
[apply] invariants
·
Invariant.withEarlyReturn (onReturn := fun r letMuts => ⌜l.Nodup ∧ (r = true ↔ l.Nodup)⌝) (onContinue :=
fun xs letMuts => ⌜xs.prefix = [] ∧ letMuts = ∅ ∨ xs.suffix = [] ∧ l.Nodup⌝)
```
:::
:::paragraph
Now consider the following direct (and excessively golfed) proof without {tactic}`mvcgen`:
```lean
theorem nodup_correct_directly (l : List Int) : nodup l ↔ l.Nodup := by
rw [nodup]
generalize hseen : (∅ : Std.HashSet Int) = seen
change ?lhs ↔ l.Nodup
suffices h : ?lhs ↔ l.Nodup ∧ ∀ x ∈ l, x ∉ seen by grind
clear hseen
induction l generalizing seen with grind [Id.run_pure, Id.run_bind]
```
:::
:::paragraph
Some observations:
* The proof is even shorter than the one with {tactic}`mvcgen`.
* The use of {tactic}`generalize` to generalize the accumulator relies on there being exactly one occurrence of {lean (type := "Std.HashSet Int")}`∅` to generalize. If that were not the case, we would have to copy parts of the program into the proof. This is a no-go for larger functions.
* {tactic}`grind` splits along the control flow of the function and reasons about {name}`Id`, given the right lemmas.
While this works for {name}`Id.run_pure` and {name}`Id.run_bind`, it would not work for {name}`Id.run_seq`, for example, because that lemma is not {tech (key := "E-matching")}[E-matchable].
If {tactic}`grind` would fail, we would be forced to do all the control flow splitting and monadic reasoning by hand until {tactic}`grind` could pick up again.
:::
The usual way to avoid replicating the control flow of a definition in a proof is to use the {tactic}`fun_cases` or {tactic}`fun_induction` tactics.
Unfortunately, {tactic}`fun_cases` does not help with control flow inside a {name}`forIn` application.
The {tactic}`mvcgen` tactic, on the other hand, ships with support for many {name}`forIn` implementations.
It can easily be extended (with {attrs}`@[spec]` annotations) to support custom {name}`forIn` implementations.
Furthermore, an {tactic}`mvcgen`-powered proof will never need to copy any part of the original program.
# Compositional Reasoning About Effectful Programs Using Hoare Triples
:::leanSection
```lean -show
variable (M : Type u → Type v) [Monad M] (α : Type u)
axiom M.run : M α → β → α
```
The previous examples reasoned about functions defined using {lean}`Id.run`{lit}` `{keywordOf Lean.Parser.Term.do}`do`{lit}` <prog>` to make use of local mutability and early return in {lit}`<prog>`.
However, real-world programs often use {keywordOf Lean.Parser.Term.do}`do` notation and monads {lean}`M` to hide away state and failure conditions as implicit “effects”.
In this use case, functions usually omit the {name}`M.run`.
Instead they have a monadic return type {lean}`M α` and compose well with other functions of that return type.
In other words, the monad is part of the function's _interface_, not merely its implementation.
:::
:::leanFirst
Here is an example involving a stateful function {name}`mkFresh` that returns auto-incremented counter values:
```lean
structure Supply where
counter : Nat
def mkFresh : StateM Supply Nat := do
let n ← (·.counter) <$> get
modify fun s => { s with counter := s.counter + 1 }
pure n
def mkFreshN (n : Nat) : StateM Supply (List Nat) := do
let mut acc := #[]
for _ in [:n] do
acc := acc.push (← mkFresh)
pure acc.toList
```
:::
::::leanFirst
:::leanSection
```lean -show
variable (n : Nat)
```
{lean}`mkFreshN n` returns {lean}`n` “fresh” numbers, modifying the internal {name}`Supply` state through {name}`mkFresh`.
Here, “fresh” refers to all previously generated numbers being distinct from the next generated number.
We can formulate and prove a correctness property {name}`mkFreshN_correct` in terms of {name}`List.Nodup`: the returned list of numbers should contain no duplicates.
In this proof, {name}`StateM.of_wp_run'_eq` serves the same role that {name}`Id.of_wp_run_eq` did in the preceding examples.
:::
```lean
theorem mkFreshN_correct (n : Nat) : ((mkFreshN n).run' s).Nodup := by
-- Focus on `(mkFreshN n).run' s`.
generalize h : (mkFreshN n).run' s = x
apply StateM.of_wp_run'_eq h
-- Show something about monadic program `mkFresh n`.
-- The `mkFreshN` and `mkFresh` arguments to `mvcgen` add to an
-- internal `simp` set and makes `mvcgen` unfold these definitions.
mvcgen [mkFreshN, mkFresh]
invariants
-- Invariant: The counter is larger than any accumulated number,
-- and all accumulated numbers are distinct.
-- Note that the invariant may refer to the state through function
-- argument `state : Supply`. Since the next number to accumulate is
-- the counter, it is distinct to all accumulated numbers.
· ⇓⟨xs, acc⟩ state =>
⌜(∀ x ∈ acc, x < state.counter) ∧ acc.toList.Nodup⌝
with grind
```
::::
## Hoare Triples
:::::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [Monad m] [WP m ps] {α σ ε : Type u} {P : Assertion ps} {Q : PostCond α ps} {prog : m α} {c : Nat}
```
A {tech}_Hoare triple_ consists of a precondition, a statement, and a postcondition; it asserts that if the precondition holds, then the postcondition holds after running the statement.
In Lean syntax, this is written {lean}`⦃ P ⦄ prog ⦃ Q ⦄`, where {lean}`P` is the precondition, {typed}`prog : m α` is the statement, and {lean}`Q` is the postcondition.
{lean}`P` and {lean}`Q` are written in an assertion language that is determined by the specific monad {lean}`m`.{margin}[In particular, monad's instance of the type class {name}`WP` specifies the ways in which assertions may refer to the monad's state or the exceptions it may throw.]
:::leanSection
```lean -show
variable {stmt1 stmt2 : m PUnit} {ps : PostShape.{0}} {P : Assertion ps} {Q : PostCond Unit ps} {P' : Assertion ps} {Q' : PostCond Unit ps}
```
Specifications as Hoare triples are compositional because they allow statements to be sequenced.
Given {lean}`⦃P⦄ stmt1 ⦃Q⦄` and {lean}`⦃P'⦄ stmt2 ⦃Q'⦄`, if {lean}`Q` implies {lean}`P'` then {lean}`⦃P⦄ (do stmt1; stmt2) ⦃Q'⦄`.
Just as proofs about ordinary functions can rely on lemmas about the functions that they call, proofs about monadic programs can use lemmas that are specified in terms of Hoare triples.
:::
::::paragraph
One suitable specification for {name}`mkFresh` as a Hoare triple is this translation of {name}`mkFreshN_correct`:
:::leanSection
```lean -show
variable {n : Nat}
```
```leanTerm
⦃⌜True⌝⦄ mkFreshN n ⦃⇓ r => ⌜r.Nodup⌝⦄
```
:::
```lean -show
variable {p : Prop}
```
Corner brackets embed propositions into the monadic assertion language, so {lean}`⌜p⌝` is the assertion of the proposition {lean}`p`.
The precondition {lean}`⌜True⌝` asserts that {lean}`True` is true; this trivial precondition is used to state that the specification imposes no requirements on the state in which it is called.
The postcondition states that the result value is a list with no duplicate elements.
::::
::::paragraph
A specification for the single-step {name}`mkFresh` describes its effects on the monad's state:
:::leanSection
```lean -show
variable {n : Nat}
```
```leanTerm
∀ (c : Nat),
⦃fun state => ⌜state.counter = c⌝⦄
mkFresh
⦃⇓ r state => ⌜r = c ∧ c < state.counter⌝⦄
```
When working in a state monad, preconditions may be parameterized over the value of the state prior to running the code.
Here, the universally quantified {name}`Nat` is used to _relate_ the initial state to the final state; the precondition is used to connect it to the initial state.
Similarly, the postcondition may also accept the final state as a parameter.
This Hoare triple states:
> If {lean}`c` refers to the {name}`Supply.counter` field of the {name}`Supply` prestate, then running {name}`mkFresh` returns {lean}`c` and modifies the {name}`Supply.counter` of the poststate to be larger than {lean}`c`.
Note that this specification is lossy: {name}`mkFresh` could increment its state by an arbitrary non-negative amount and still satisfy the specification.
This is good, because specifications may _abstract over_ uninteresting implementation details, ensuring resilient and small proofs.
:::
::::
:::paragraph
Hoare triples are defined in terms of a logic of stateful predicates plus a {tech}[weakest precondition] semantics {lean}`wp⟦prog⟧` that translates monadic programs into this logic.
A weakest precondition semantics is an interpretation of programs as mappings from postconditions to the weakest precondition that the program would require to ensure the postcondition; in this interpretation, programs are understood as {tech (key := "predicate transformer semantics")}_predicate transformers_.
The Hoare triple syntax is notation for {name}`Std.Do.Triple`:
```lean -keep
-- This is the definition of Std.Do.Triple:
def Triple [WP m ps] {α : Type u} (prog : m α)
(P : Assertion ps) (Q : PostCond α ps) : Prop :=
P ⊢ₛ wp⟦prog⟧ Q
```
:::
```lean -show
variable {σ : Type u}
```
:::paragraph
The {name}`WP` type class maps a monad {lean}`m` to its {name}`PostShape` {lean}`ps`, and this {name}`PostShape` governs the exact shape of the {name}`Std.Do.Triple`.
Many of the standard monad transformers such as {name}`StateT`, {name}`ReaderT` and {name}`ExceptT` come with a canonical {name}`WP` instance.
For example, {lean}`StateT σ` comes with a {name}`WP` instance that adds a {lean}`σ` argument to every {name}`Assertion`.
Stateful entailment `⊢ₛ` eta-expands through these additional {lean}`σ` arguments.
For {name}`StateM` programs, the following type is definitionally equivalent to {name}`Std.Do.Triple`:
```lean
def StateMTriple {α σ : Type u} (prog : StateM σ α)
(P : σ → ULift Prop) (Q : (α → σ → ULift Prop) × PUnit) : Prop :=
∀ s, (P s).down → let (a, s') := prog.run s; (Q.1 a s').down
```
```lean -show
example : @StateMTriple α σ = Std.Do.Triple (m := StateM σ) := rfl
```
:::
```lean -show
variable {p : Prop}
```
The common postcondition notation `⇓ r => ...` injects an assertion of type {lean}`α → Assertion ps` into
{lean}`PostCond α ps` (the `⇓` is meant to be parsed like `fun`); in case of {name}`StateM` by adjoining it with an empty tuple {name}`PUnit.unit`.
The shape of postconditions becomes more interesting once exceptions enter the picture.
The notation {lean}`⌜p⌝` embeds a pure hypotheses {lean}`p` into a stateful assertion.
Vice versa, any stateful hypothesis {lean}`P` is called _pure_ if it is equivalent to {lean}`⌜p⌝`
for some {lean}`p`.
Pure, stateful hypotheses may be freely moved into the regular Lean context and back.
(This can be done manually with the {tactic}`mpure` tactic.)
:::::
## Composing Specifications
Nested unfolding of definitions as in {tactic}`mvcgen`{lit}` [`{name}`mkFreshN`{lit}`, `{name}`mkFresh`{lit}`]` is quite blunt but effective for small programs.
A more compositional way is to develop individual {tech}_specification lemmas_ for each monadic function.
A specification lemma is a Hoare triple that is automatically used during {tech}[verification condition] generation to obtain the pre- and postconditions of each statement in a {keywordOf Lean.Parser.Term.do}`do`-block.
When the system cannot automatically prove that the postcondition of one statement implies the precondition of the next, then this missing reasoning step becomes a verification condition.
:::paragraph
Specification lemmas can either be passed as arguments to {tactic}`mvcgen` or registered in a global (or {keyword}`scoped`, or {keyword}`local`) database of specifications using the {attrs}`@[spec]` attribute:
```lean
@[spec]
theorem mkFresh_spec (c : Nat) :
⦃fun state => ⌜state.counter = c⌝⦄
mkFresh
⦃⇓ r state => ⌜r = c ∧ c < state.counter⌝⦄ := by
-- Unfold `mkFresh` and blast away:
mvcgen [mkFresh] with grind
@[spec]
theorem mkFreshN_spec (n : Nat) :
⦃⌜True⌝⦄ mkFreshN n ⦃⇓ r => ⌜r.Nodup⌝⦄ := by
-- `mvcgen [mkFreshN, mkFresh_spec]` if `mkFresh_spec` were not
-- registered with `@[spec]`
mvcgen [mkFreshN]
invariants
-- As before:
· ⇓⟨xs, acc⟩ state =>
⌜(∀ x ∈ acc, x < state.counter) ∧ acc.toList.Nodup⌝
with grind
```
:::
:::paragraph
The original correctness theorem can now be proved using {tactic}`mvcgen` alone:
```lean
theorem mkFreshN_correct_compositional (n : Nat) :
((mkFreshN n).run' s).Nodup := by
generalize h : (mkFreshN n).run' s = x
apply StateM.of_wp_run'_eq h
mvcgen
```
The specification lemma {name}`mkFreshN_spec` is automatically used by {tactic}`mvcgen`.
:::
## An Advanced Note About Pure Preconditions and a Notion of Frame Rule
This subsection is a bit of a digression and can be skipped on first reading.
:::leanSection
```lean -show
axiom M : Type → Type
variable {x y : UInt8} [Monad M] [WP M .pure]
def addQ (x y : UInt8) : M UInt8 := pure (x + y)
local infix:1023 " +? " => addQ
axiom dots {α} : α
local notation "…" => dots
```
Say the specification for some [`Aeneas`](https://github.com/AeneasVerif/aeneas)-inspired monadic addition function {typed}`x +? y : M UInt8` has the
requirement that the addition won't overflow, that is, `h : x.toNat + y.toNat ≤ UInt8.size`.
Should this requirement be encoded as a regular Lean hypothesis of the specification (`add_spec_hyp`) or should this requirement be encoded as a pure precondition of the Hoare triple, using `⌜·⌝` notation (`add_spec_pre`)?
```lean
theorem add_spec_hyp (x y : UInt8)
(h : x.toNat + y.toNat ≤ UInt8.size) :
⦃⌜True⌝⦄ x +? y ⦃⇓ r => ⌜r.toNat = x.toNat + y.toNat⌝⦄ := …
theorem add_spec_pre (x y : UInt8) :
⦃⌜x.toNat + y.toNat ≤ UInt8.size⌝⦄
x +? y
⦃⇓ r => ⌜r.toNat = x.toNat + y.toNat⌝⦄ := …
```
:::
The first approach is advisable, although it should not make a difference in practice.
The VC generator will move pure hypotheses from the stateful context into the regular Lean context, so the second form turns effectively into the first form.
This is referred to as {deftech}_framing_ hypotheses (cf. the {tactic}`mpure` and {tactic}`mframe` tactics).
Hypotheses in the Lean context are part of the immutable {deftech}_frame_ of the stateful logic, because in contrast to stateful hypotheses they survive the rule of consequence.
# Monad Transformers and Lifting
Real-world programs often use monads that are built from multiple {tech}[monad transformers], with operations being frequently {ref "lifting-monads"}[lifted] from one monad to another.
Verification of these programs requires taking this into account.
We can tweak the previous example to demonstrate this.
```lean -show
namespace Transformers
variable {m : Type → Type} {α : Type} {ps : PostShape.{0}}
attribute [-instance] Lake.instMonadLiftTOfMonadLift_lake
```
::::paragraph
:::leanFirst
Now, there is an application with two separate monads, both built using transformers:
```lean
abbrev CounterM := StateT Supply (ReaderM String)
abbrev AppM := StateT Bool CounterM
```
Instead of using {lean}`StateM Supply`, {name}`mkFresh` uses {lean}`CounterM`:
```lean
def mkFresh : CounterM Nat := do
let n ← (·.counter) <$> get
modify fun s => { s with counter := s.counter + 1 }
pure n
```
{name}`mkFreshN` is defined in terms of {name}`AppM`, which includes multiple states and a reader effect.
The definition of {name}`mkFreshN` lifts {name}`mkFresh` into {name}`AppM`:
```lean
def mkFreshN (n : Nat) : AppM (List Nat) := do
let mut acc := #[]
for _ in [:n] do
let n ← mkFresh
acc := acc.push n
return acc.toList
```
:::
::::
::::paragraph
Then the {tactic}`mvcgen`-based proof goes through unchanged:
```lean
@[spec]
theorem mkFresh_spec (c : Nat) :
⦃fun state => ⌜state.counter = c⌝⦄
mkFresh
⦃⇓ r state => ⌜r = c ∧ c < state.counter⌝⦄ := by
--TODO: mvcgen [mkFresh] with grind
sorry
@[spec]
theorem mkFreshN_spec (n : Nat) :
⦃⌜True⌝⦄ mkFreshN n ⦃⇓ r => ⌜r.Nodup⌝⦄ := by
-- `liftCounterM` here ensures unfolding
mvcgen [mkFreshN]
invariants
· ⇓⟨xs, acc⟩ _ state =>
⌜(∀ n ∈ acc, n < state.counter) ∧ acc.toList.Nodup⌝
with grind
```
::::
:::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [WP m ps] {α : Type u} {prog : m α}
```
The {name}`WPMonad` type class asserts that {lean}`wp⟦prog⟧` distributes over the {name}`Monad` operations (“monad morphism”).
This proof might not look much more exciting than when only a single monad was involved.
However, under the radar of the user the proof builds on a cascade of specifications for {name}`MonadLift` instances.
:::
```lean -show
end Transformers
```
# Exceptions
::::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [WP m ps] {P : Assertion ps} {α : Type u} {prog : m α} {Q' : α → Assertion ps}
```
If {keyword}`let mut` is the {keywordOf Lean.Parser.Term.do}`do`-equivalent of {name}`StateT`, then early {keywordOf Lean.Parser.Term.doReturn}`return` is the equivalent of {name}`ExceptT`.
We have seen how the {tactic}`mvcgen` copes with {name}`StateT`; here we will look at the program logic's support for {name}`ExceptT`.
Exceptions are the reason why the type of postconditions {lean}`PostCond α ps` is not simply a single condition of type {lean}`α → Assertion ps` for the success case.
To see why, suppose the latter was the case, and suppose that program {lean}`prog` throws an exception in a prestate satisfying {lean}`P`.
Should we be able to prove {lean}`⦃P⦄ prog ⦃⇓ r => Q' r⦄`?
(Recall that `⇓` is grammatically similar to `fun`.)
There is no result `r`, so it is unclear what this proof means for {lean}`Q'`!
So there are two reasonable options, inspired by non-termination in traditional program logics:
: The {tech}_total correctness interpretation_
{lean}`⦃P⦄ prog ⦃⇓ r => Q' r⦄` asserts that, given {lean}`P` holds, then {lean}`prog` terminates _and_ {lean}`Q'` holds for the result.
: The {tech}_partial correctness interpretation_
{lean}`⦃P⦄ prog ⦃⇓? r => Q' r⦄` asserts that, given {lean}`P` holds, and _if_ {lean}`prog` terminates _then_ {lean}`Q'` holds for the result.
The notation {lean}`⇓ r => Q' r` has the total interpretation, while {lean}`⇓? r => Q' r` has the partial interpretation.
In the running example, {lean}`⦃P⦄ prog ⦃⇓ r => Q' r⦄` is unprovable, but {lean}`⦃P⦄ prog ⦃⇓? r => Q' r⦄` is trivially provable.
However, the binary choice suggests that there is actually a _spectrum_ of correctness properties to express.
The notion of postconditions {name}`PostCond` in `Std.Do` supports this spectrum.
::::
```lean -show
namespace Exceptions
```
For example, suppose that our {name}`Supply` of fresh numbers is bounded and we want to throw an exception if the supply is exhausted.
Then {name}`mkFreshN` should throw an exception _only if_ the supply is indeed exhausted, as in this implementation:
```lean
structure Supply where
counter : Nat
limit : Nat
property : counter ≤ limit
def mkFresh : EStateM String Supply Nat := do
let supply ← get
if h : supply.counter = supply.limit then
throw s!"Supply exhausted: {supply.counter} = {supply.limit}"
else
let n := supply.counter
have := supply.property
set { supply with counter := n + 1, property := by grind }
pure n
```
The following correctness property expresses this:
```lean
@[spec]
theorem mkFresh_spec (c : Nat) :
⦃fun state => ⌜state.counter = c⌝⦄
mkFresh
⦃post⟨fun r state => ⌜r = c ∧ c < state.counter⌝,
fun _ state => ⌜c = state.counter ∧ c = state.limit⌝⟩⦄ := by
mvcgen [mkFresh] with grind
```
In this property, the postcondition has two branches: the first covers successful termination, and the second applies when an exception is thrown.
The monad's {name}`WP` instance determines both how many branches the postcondition may have and the number of parameters in each branch: each exception that might be triggered gives rise to an extra branch, and each state gives an extra parameter.
:::leanFirst
In this new monad, {name}`mkFreshN`'s implementation is unchanged, except for the type signature:
```lean
def mkFreshN (n : Nat) : EStateM String Supply (List Nat) := do
let mut acc := #[]
for _ in [:n] do
acc := acc.push (← mkFresh)
pure acc.toList
```
:::
:::paragraph
However, the specification lemma must account for both successful termination and exceptions being thrown, in both the postcondition and the loop invariant:
```lean
@[spec]
theorem mkFreshN_spec (n : Nat) :
⦃⌜True⌝⦄
mkFreshN n
⦃post⟨fun r => ⌜r.Nodup⌝,
fun _msg state => ⌜state.counter = state.limit⌝⟩⦄ := by
mvcgen [mkFreshN]
invariants
· post⟨fun ⟨xs, acc⟩ state =>
⌜(∀ n ∈ acc, n < state.counter) ∧ acc.toList.Nodup⌝,
fun _msg state => ⌜state.counter = state.limit⌝⟩
with grind
```
:::
:::paragraph
The final proof uses the specification lemmas and {tactic}`mvcgen`, just as before:
```lean
theorem mkFreshN_correct (n : Nat) :
match (mkFreshN n).run s with
| .ok l _ => l.Nodup
| .error _ s' => s'.counter = s'.limit := by
generalize h : (mkFreshN n).run s = x
apply EStateM.of_wp_run_eq h
mvcgen
```
:::
```lean -show
end Exceptions
```
:::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [WP m ps] {P : Assertion ps} {α σ ε : Type u} {prog : m α} {Q' : α → Assertion ps}
```
Just as any {lean}`StateT σ`-like monad transformer gives rise to a {lean}`PostShape.arg σ` layer in the {lean}`ps` that {name}`WP` maps into, any {lean}`ExceptT ε`-like layer gives rise to a {lean}`PostShape.except ε` layer.
Every {lean}`PostShape.arg σ` adds another `σ → ...` layer to the language of {lean}`Assertion`s.
Every {lean}`PostShape.except ε` leaves the {lean}`Assertion` language unchanged, but adds another exception
condition to the postcondition.
Hence the {name}`WP` instance for {lean}`EStateM ε σ` maps to the {name}`PostShape` {lean}`PostShape.except ε (.arg σ .pure)`, just
as for {lean}`ExceptT ε (StateM σ)`.
:::
# Extending `mvcgen` With Support for Custom Monads
The {tactic}`mvcgen` framework is designed to be extensible.
None of the monads presented so far have in any way been hard-coded into {tactic}`mvcgen`.
Rather, {tactic}`mvcgen` relies on instances of the {name}`WP` and {name}`WPMonad` type class and user-provided specifications to generate {tech}[verification conditions].
:::leanSection
```lean -show
variable {m : Type u → Type v} [Monad m] {ps : PostShape.{u}}
```
The {name}`WP` instance defines the weakest precondition interpretation of a monad {lean}`m` into a predicate transformer {lean}`PredTrans ps`,
and the matching {name}`WPMonad` instance asserts that this translation distributes over the {name}`Monad` operations.
:::
::::paragraph
:::leanFirst
Suppose one wants to use `mvcgen` to generate verification conditions for programs generated by [`Aeneas`](https://github.com/AeneasVerif/aeneas).
`Aeneas` translates Rust programs into Lean programs in the following {name}`Result` monad:
```lean
inductive Error where
| integerOverflow: Error
-- ... more error kinds ...
inductive Result (α : Type u) where
| ok (v: α): Result α
| fail (e: Error): Result α
| div
```
```lean -show
instance Result.instMonad : Monad Result where
pure x := .ok x
bind x f := match x with
| .ok v => f v
| .fail e => .fail e
| .div => .div
instance Result.instLawfulMonad : LawfulMonad Result := by
-- TODO: Replace sorry with grind when it no longer introduces section
-- variables
apply LawfulMonad.mk' <;> (simp only [Result.instMonad]; sorry)
```
:::
::::
:::paragraph
There are both {inst}`Monad Result` and {inst}`LawfulMonad Result` instances.
Supporting this monad in {tactic}`mvcgen` is a matter of:
1. Adding {name}`WP` and {name}`WPMonad` instances for {name}`Result`
2. Registering specification lemmas for the translation of basic Rust primitives such as addition etc.
:::
::::paragraph
:::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [WP m ps] {P : Assertion ps} {α σ ε : Type u} {prog : m α} {Q' : α → Assertion ps}
```
The {name}`WP` instance for {name}`Result` specifies a postcondition shape {lean (type := "PostShape.{0}")}`.except Error .pure` because there are no state-like effects, but there is a single exception of type {lean}`Error`.
The {name}`WP` instance translates programs in {lean}`Result α` to predicate transformers in {lean}`PredTrans ps α`.
That is, a function in {lean}`PostCond α ps → Assertion ps`, mapping a postcondition to its weakest precondition.
The implementation of {name}`WP.wp` reuses the implementation for {lean}`Except Error` for two of its cases, and maps diverging programs to {lean}`False`.
The instance is named so that it can be more easily unfolded in proofs about it.
:::
```lean
instance Result.instWP : WP Result (.except Error .pure) where
wp
| .ok v => wp (pure v : Except Error _)
| .fail e => wp (throw e : Except Error _)
| .div => PredTrans.const ⌜False⌝
```
::::
:::paragraph
The implementation of {name}`WP.wp` should distribute over the basic monad operators:
```lean
instance : WPMonad Result (.except Error .pure) where
wp_pure := by
intros
ext Q
simp [wp, PredTrans.pure, pure, Except.pure, Id.run]
wp_bind x f := by
simp only [Result.instWP, bind]
ext Q
cases x <;> simp [PredTrans.bind, PredTrans.const]
```
:::
```lean
theorem Result.of_wp {α} {x : Result α} (P : Result α → Prop) :
(⊢ₛ wp⟦x⟧ post⟨fun a => ⌜P (.ok a)⌝,
fun e => ⌜P (.fail e)⌝⟩) → P x := by
intro hspec
simp only [instWP] at hspec
split at hspec <;> simp_all
```
:::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [WP m ps] {P : Assertion ps} {α σ ε : Type u} {prog : m α} {Q' : α → Assertion ps}
```
The definition of the {name}`WP` instance determines what properties can be derived from proved specifications via {lean}`Result.of_wp`.
This lemma defines what “weakest precondition” means.
:::
:::paragraph
To exemplify the second part, here is an example definition of {name}`UInt32` addition in {name}`Result` that models integer overflow:
```lean
instance : MonadExcept Error Result where
throw e := .fail e
tryCatch x h := match x with
| .ok v => pure v
| .fail e => h e
| .div => .div
def addOp (x y : UInt32) : Result UInt32 :=
if x.toNat + y.toNat ≥ UInt32.size then
throw .integerOverflow
else
pure (x + y)
```
:::
:::paragraph
There are two relevant specification lemmas to register:
```lean
@[spec]
theorem Result.throw_spec {α Q} (e : Error) :
⦃Q.2.1 e⦄ throw (m := Result) (α := α) e ⦃Q⦄ := id
@[spec]
theorem addOp_ok_spec {x y} (h : x.toNat + y.toNat < UInt32.size) :
⦃⌜True⌝⦄
addOp x y
⦃⇓ r => ⌜r = x + y ∧ (x + y).toNat = x.toNat + y.toNat⌝⦄ := by
mvcgen [addOp] with (simp_all; try grind)
```
:::
:::paragraph
This is already enough to prove the following example:
```lean
example :
⦃⌜True⌝⦄
do let mut x ← addOp 1 3
for _ in [:4] do
x ← addOp x 5
return x
⦃⇓ r => ⌜r.toNat = 24⌝⦄ := by
mvcgen
invariants
· ⇓⟨xs, x⟩ => ⌜x.toNat = 4 + 5 * xs.prefix.length⌝
with (simp_all [UInt32.size]; try grind)
```
:::
# Proof Mode for Stateful Goals
```lean -show
variable {σs : List (Type u)} {H T : SPred σs}
```
It is a priority of {tactic}`mvcgen` to break down monadic programs into {tech}[verification conditions] that are straightforward to understand.
For example, when the monad is monomorphic and all loop invariants have been instantiated, an invocation of {tactic}`all_goals`{lit}` `{tactic}`mleave` should simplify away any {name}`Std.Do.SPred`-specific constructs and leave behind a goal that is easily understood by humans and {tactic}`grind`.
This {tactic}`all_goals`{lit}` `{tactic}`mleave` step is carried out automatically by {tactic}`mvcgen` after loop invariants have been instantiated.
However, there are times when {tactic}`mleave` will be unable to remove all {name}`Std.Do.SPred` constructs.
In this case, verification conditions of the form {lean}`H ⊢ₛ T` will be left behind.
The assertion language {name}`Assertion` translates into an {name}`Std.Do.SPred` as follows:
```lean -keep
abbrev PostShape.args : PostShape.{u} → List (Type u)
| .pure => []
| .arg σ s => σ :: PostShape.args s
| .except _ s => PostShape.args s
abbrev Assertion (ps : PostShape.{u}) : Type u :=
SPred (PostShape.args ps)
```
:::leanSection
```lean -show
universe u v
variable {m : Type u → Type v} {ps : PostShape.{u}} [WP m ps] {P : Assertion ps} {α σ ε : Type u} {prog : m α} {Q' : α → Assertion ps}
```
A common case for when a VC of the form {lean}`H ⊢ₛ T` is left behind is when the base monad {lean}`m` is polymorphic.
In this case, the proof will depend on a {lean}`WP m ps` instance which governs the translation into the {name}`Assertion` language, but the exact correspondence to `σs : List (Type u)` is yet unknown.
To successfully discharge such a VC, `mvcgen` comes with an entire proof mode that is inspired by that of the Iris concurrent separation logic.
(In fact, the proof mode was adapted in large part from its Lean clone, [`iris-lean`](https://github.com/leanprover-community/iris-lean).)
The {ref "tactic-ref-spred"}[tactic reference] contains a list of all proof mode tactics.
:::