Skip to content

Commit 1b9f9b2

Browse files
committed
While loop (no measure)
1 parent 718ea04 commit 1b9f9b2

4 files changed

Lines changed: 141 additions & 7 deletions

File tree

‎RandomDo/Monad/Instances.lean‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ instance {m : Type u → Type v} [Monad m] :
2929
mPure := pure
3030
mBind := bind
3131

32+
/-- The unbounded loop behind `while … rdo`, at a core monad, is core's loop over `Lean.Loop`. -/
33+
instance {m : Type u → Type v} [Monad m] :
34+
MeasurableSpaceForIn (Monad.toMeasurableSpaceMonad m) Lean.Loop Unit where
35+
forIn xs b f := ForIn.forIn (m := m) xs b f
36+
3237
/-- A measurable space monad for pseudo random number generation. -/
3338
abbrev PseudoRandomM := Monad.toMeasurableSpaceMonad Rand
3439

‎RandomDo/Monad/Notation.lean‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,17 @@ def rdoForDecl := leading_parser
260260
dec.continueWithUnit
261261
mkBindApp σ γ forIn rest
262262

263+
/-- parser for `rdo` while loops -/
264+
@[doElem_parser] def rdoWhile := leading_parser
265+
"while " >> withForbidden "rdo" doIfCond >> " rdo " >> doSeq
266+
267+
/-- Define expander for `while` loops in `rdo` notation. As in core, `while c rdo body` is a loop
268+
over `Loop.mk` that runs `body` while `c` holds and breaks otherwise. -/
269+
@[macro rdoWhile] def expandRDoWhile : Macro
270+
| `(rdoWhile| while%$tk $cond:doIfCond rdo $body) =>
271+
`(doElem| for%$tk _ in Lean.Loop.mk rdo if $cond:doIfCond then $body else break)
272+
| _ => Macro.throwUnsupported
273+
263274
/-- Infer the `ControlInfo` of an `rdo` loop as that of the core `for` loop with the same body. -/
264275
@[doElem_control_info rdoFor] def controlInfoRDoFor : ControlInfoHandler := fun stx => do
265276
let `(rdoFor| for $_:rdoForDecl,* rdo $body) := stx | throwUnsupportedSyntax

‎Test/Gaps.lean‎

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,42 @@ namespace Test.Gaps
2020

2121
/-! ## Unbounded and conditional iteration
2222
23-
TODO: `while`, `repeat` and `repeat … until` all expand to `for _ in Loop.mk do …`, which reaches
24-
core's `doFor` and so asks for a `ForIn` instance. Supporting them needs the macros re-pointed at
25-
`rdoFor` and, at `Measure`, a denotation for an iteration that need not terminate.
23+
`while … rdo` is a loop over `Lean.Loop`, which has an instance at the core monads only.
24+
TODO: at `Measure`, a denotation for an iteration that need not terminate: the least fixed point of
25+
its unfolding, where the runs that never stop carry no mass. And `repeat` and `repeat … until`,
26+
which still expand to core's `for _ in Loop.mk do …`, need `rdo` counterparts.
2627
-/
2728

29+
/--
30+
error: failed to synthesize instance of type class
31+
MeasurableSpaceForIn Measure Lean.Loop ?α
32+
33+
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
34+
-/
35+
#guard_msgs in
36+
noncomputable def whileAtMeasure : Measure ℕ := rdo
37+
let mut n := 0
38+
let mut go := true
39+
while go rdo
40+
let b ← fairCoin
41+
n := n + 1
42+
if b then
43+
go := false
44+
return n
45+
2846
/--
2947
error: failed to synthesize instance of type class
3048
ForIn IdM Lean.Loop ?α
3149
3250
Hint: Type class instance resolution failures can be inspected with the `set_option trace.Meta.synthInstance true` command.
3351
-/
3452
#guard_msgs in
35-
def whileLoop : IdM ℕ := rdo
53+
def repeatLoop : IdM ℕ := rdo
3654
let mut i := 0
37-
while i < 3 do
55+
repeat
3856
i := i + 1
57+
if 3 ≤ i then
58+
break
3959
return i
4060

4161
/-! ## Exceptions

‎Test/Loops.lean‎

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
module
22

33
public import Test.Common
4+
meta import Test.Common
5+
public import Std.Tactic.Do
46

57
set_option linter.style.header false
8+
set_option linter.hashCommand false
69

710
/-!
8-
# `rdo`: `for` loops over a single collection
11+
# `rdo`: `for` and `while` loops
912
1013
`rdo` has its own `for … rdo …` parser, expander and elaborator, mirroring core's but emitting
11-
`MeasurableSpaceForIn.forIn`. Instances exist for `List`, `Array` and `Vector`.
14+
`MeasurableSpaceForIn.forIn`. Instances exist for `List`, `Array` and `Vector`, and for `Lean.Loop`,
15+
which `while … rdo` loops over, at the core monads.
1216
1317
A loop can sit under another construct, including another loop: the enclosing one learns what the
1418
loop does to the control flow from the `ControlInfo` handler of `rdoFor`, which is that of core's
@@ -235,6 +239,100 @@ noncomputable def countPairsOfHeads (n : ℕ) : Measure ℕ := rdo
235239
c := c + 1
236240
return c
237241

242+
/-! ## `while` loops
243+
244+
`while c rdo body` is a loop over `Lean.Loop`, as in core. At a core monad it is core's loop, which
245+
the kernel cannot unfold, so these programs are checked with `#guard` rather than `rfl`, and proved
246+
through `mvcgen`. There is no instance at `Measure` yet.
247+
-/
248+
249+
/-- A `while` loop, counting down from `n`. -/
250+
def countdown (n : ℕ) : IdM ℕ := rdo
251+
let mut i := n
252+
let mut steps := 0
253+
while 0 < i rdo
254+
i := i - 1
255+
steps := steps + 1
256+
return steps
257+
258+
#guard IdM.run (countdown 5) = 5
259+
260+
#guard IdM.run (countdown 0) = 0
261+
262+
open Std.Do in
263+
set_option mvcgen.warning false in
264+
theorem countdown_eq (n : ℕ) : IdM.run (countdown n) = n := by
265+
generalize h : IdM.run (countdown n) = r
266+
apply Id.of_wp_run_eq h
267+
simp only [countdown, MeasurableSpaceForIn.forIn, MeasurableSpaceBind.mBind,
268+
MeasurableSpacePure.mPure]
269+
dsimp only [IdM, Monad.toMeasurableSpaceMonad]
270+
mvcgen invariants
271+
· fun st => ⟨st.1⟩
272+
· ⇓ c => match c with
273+
| .inl st => ⌜st.1 + st.2 = n⌝
274+
| .inr st => ⌜st.2 = n⌝
275+
all_goals simp_all <;> omega
276+
277+
/-- `break` out of a `while` loop. -/
278+
def halveUntilOdd (n : ℕ) : IdM ℕ := rdo
279+
let mut k := n
280+
while 0 < k rdo
281+
if k % 2 = 1 then
282+
break
283+
k := k / 2
284+
return k
285+
286+
#guard IdM.run (halveUntilOdd 24) = 3
287+
288+
#guard IdM.run (halveUntilOdd 0) = 0
289+
290+
/-- An early `return` out of a `while` loop. -/
291+
def firstSquareAbove (n : ℕ) : IdM ℕ := rdo
292+
let mut k := 0
293+
while true rdo
294+
if k * k > n then
295+
return k
296+
k := k + 1
297+
return 0
298+
299+
#guard IdM.run (firstSquareAbove 10) = 4
300+
301+
/-- `while let`, consuming a list one element at a time. -/
302+
def sumByPopping (xs : List ℕ) : IdM ℕ := rdo
303+
let mut rest := xs
304+
let mut s := 0
305+
while let x :: xs' := rest rdo
306+
s := s + x
307+
rest := xs'
308+
return s
309+
310+
#guard IdM.run (sumByPopping [1, 2, 3]) = 6
311+
312+
/-- `while h : c`, which hands the body a proof of the condition. -/
313+
def countdownWithProof (n : ℕ) : IdM ℕ := rdo
314+
let mut i := n
315+
let mut steps := 0
316+
while h : 0 < i rdo
317+
have : i - 1 < i := Nat.sub_lt h Nat.one_pos
318+
i := i - 1
319+
steps := steps + 1
320+
return steps
321+
322+
#guard IdM.run (countdownWithProof 4) = 4
323+
324+
/-- A `while` loop nested inside a `for` loop. -/
325+
def sumOfLogs (xs : List ℕ) : IdM ℕ := rdo
326+
let mut s := 0
327+
for x in xs rdo
328+
let mut k := x
329+
while 1 < k rdo
330+
k := k / 2
331+
s := s + 1
332+
return s
333+
334+
#guard IdM.run (sumOfLogs [1, 2, 8]) = 4
335+
238336
end Test.Loops
239337

240338
end

0 commit comments

Comments
 (0)