Skip to content

Commit ec9b009

Browse files
authored
feat: equivalence of iterators (#8545)
This PR provides the means to reason about "equivalent" iterators. Simply speaking, two iterators are equivalent if they behave the same as long as consumers do not introspect their states.
1 parent 50474fe commit ec9b009

File tree

21 files changed

+1606
-74
lines changed

21 files changed

+1606
-74
lines changed

src/Std/Data/Internal/LawfulMonadLiftFunction.lean

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Init.NotationExtra
1010
import Init.Control.Lawful.MonadLift
1111

1212
/-!
13-
# Typeclass for lawfule monad lifting functions
13+
# Typeclass for lawful monad lifting functions
1414
1515
This module provides a typeclass `LawfulMonadLiftFunction f` that asserts that a function `f`
1616
mapping values from one monad to another monad commutes with `pure` and `bind`. This equivalent to
@@ -60,9 +60,13 @@ theorem LawfulMonadLiftFunction.lift_seqRight [LawfulMonad m] [LawfulMonad n]
6060
lift (x *> y) = (lift x : n α) *> (lift y : n β) := by
6161
simp only [seqRight_eq, lift_map, lift_seq]
6262

63-
def instMonadLiftOfFunction {lift : ⦃α : Type u⦄ -> m α → n α} :
64-
MonadLift m n where
65-
monadLift := lift (α := _)
63+
abbrev idToMonad [Monad m] ⦃α : Type u⦄ (x : Id α) : m α :=
64+
pure x.run
65+
66+
def LawfulMonadLiftFunction.idToMonad [Monad m] [LawfulMonad m] :
67+
LawfulMonadLiftFunction (m := Id) (n := m) idToMonad where
68+
lift_pure := by simp [Internal.idToMonad]
69+
lift_bind := by simp [Internal.idToMonad]
6670

6771
instance [LawfulMonadLiftFunction lift] :
6872
letI : MonadLift m n := ⟨lift (α := _)⟩

src/Std/Data/Iterators.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ All of the following module names are prefixed with `Std.Data.Iterators`.
9797
9898
`Lemmas` provides the means to verify programs that use iterators.
9999
100+
In particular, `Lemmas.Equivalence` develops the theory of equivalences of iterators.
101+
100102
### Implementation details
101103
102104
`Internal` contains code that should not be relied upon because it may change in the future.

src/Std/Data/Iterators/Basic.lean

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Authors: Paul Reichert
66
prelude
77
import Init.Core
88
import Init.Classical
9+
import Init.Ext
910
import Init.NotationExtra
1011
import Init.TacticsExtra
1112

@@ -58,6 +59,7 @@ def x := [1, 2, 3].iterM IO
5859
def x := ([1, 2, 3].iterM IO : IterM IO Nat)
5960
```
6061
-/
62+
@[ext]
6163
structure IterM {α : Type w} (m : Type w → Type w') (β : Type w) where
6264
/-- Internal implementation detail of the iterator. -/
6365
internalState : α
@@ -204,6 +206,12 @@ theorem IterStep.mapIterator_mapIterator {α' : Type u'} {α'' : Type u''}
204206
(step.mapIterator f).mapIterator g = step.mapIterator (g ∘ f) := by
205207
cases step <;> rfl
206208

209+
theorem IterStep.mapIterator_comp {α' : Type u'} {α'' : Type u''}
210+
{f : α → α'} {g : α' → α''} :
211+
IterStep.mapIterator (β := β) (g ∘ f) = mapIterator g ∘ mapIterator f := by
212+
apply funext
213+
exact fun _ => mapIterator_mapIterator.symm
214+
207215
@[simp]
208216
theorem IterStep.mapIterator_id {step : IterStep α β} :
209217
step.mapIterator id = step := by

src/Std/Data/Iterators/Combinators/FilterMap.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ it ---a --b--c --d-e--⊥
5151
it.filterMapWithPostcondition ---a'-----c'-------⊥
5252
```
5353
54-
(given that `f a = pure (some a)'`, `f c = pure (some c')` and `f b = f d = d e = pure none`)
54+
(given that `f a = pure (some a')`, `f c = pure (some c')` and `f b = f d = d e = pure none`)
5555
5656
**Termination properties:**
5757
5858
* `Finite` instance: only if `it` is finite
59-
* `Productive` instance: only if `it` is finite`
59+
* `Productive` instance: only if `it` is finite
6060
6161
For certain mapping functions `f`, the resulting iterator will be finite (or productive) even though
6262
no `Finite` (or `Productive`) instance is provided. For example, if `f` never returns `none`, then

src/Std/Data/Iterators/Lemmas.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ import Std.Data.Iterators.Lemmas.Monadic
99
import Std.Data.Iterators.Lemmas.Consumers
1010
import Std.Data.Iterators.Lemmas.Combinators
1111
import Std.Data.Iterators.Lemmas.Producers
12+
import Std.Data.Iterators.Lemmas.Equivalence

src/Std/Data/Iterators/Lemmas/Combinators/FilterMap.lean

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ theorem Iter.step_mapM {f : β → n γ}
181181
match it.step with
182182
| .yield it' out h => do
183183
let out' ← f out
184-
pure <| .yield (it'.mapM f) out' (.yieldSome h ⟨_, rfl⟩)
184+
pure <| .yield (it'.mapM f) out' (.yieldSome h ⟨⟨out', True.intro⟩, rfl⟩)
185185
| .skip it' h =>
186186
pure <| .skip (it'.mapM f) (.skip h)
187187
| .done h =>
@@ -220,7 +220,7 @@ theorem Iter.step_filterMap {f : β → Option γ} :
220220
· simp
221221
· simp
222222

223-
def Iter.step_map {f : β → γ} :
223+
theorem Iter.step_map {f : β → γ} :
224224
(it.map f).step = match it.step with
225225
| .yield it' out h =>
226226
.yield (it'.map f) (f out) (.yieldSome (out := out) h ⟨⟨f out, rfl⟩, rfl⟩)

src/Std/Data/Iterators/Lemmas/Combinators/Monadic/FilterMap.lean

Lines changed: 197 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Paul Reichert
55
-/
66
prelude
7+
import Std.Data.Internal.LawfulMonadLiftFunction
78
import Std.Data.Iterators.Combinators.Monadic.FilterMap
89
import Std.Data.Iterators.Lemmas.Consumers.Monadic
9-
import Std.Data.Internal.LawfulMonadLiftFunction
10+
import Std.Data.Iterators.Lemmas.Equivalence.StepCongr
1011

1112
namespace Std.Iterators
1213
open Std.Internal
@@ -145,7 +146,7 @@ theorem IterM.step_mapM {γ : Type w} {f : β → n γ}
145146
match ← it.step with
146147
| .yield it' out h => do
147148
let out' ← f out
148-
pure <| .yield (it'.mapM f) out' (.yieldSome h ⟨_, rfl⟩)
149+
pure <| .yield (it'.mapM f) out' (.yieldSome h ⟨⟨out', True.intro⟩, rfl⟩)
149150
| .skip it' h =>
150151
pure <| .skip (it'.mapM f) (.skip h)
151152
| .done h =>
@@ -157,6 +158,8 @@ theorem IterM.step_mapM {γ : Type w} {f : β → n γ}
157158
simp only [PostconditionT.operation_map, bind_map_left, bind_pure_comp]
158159
simp only [PostconditionT.lift, Functor.map, Functor.map_map,
159160
bind_map_left, bind_pure_comp]
161+
simp only [PostconditionT.operation_map, Functor.map_map, PlausibleIterStep.skip,
162+
PlausibleIterStep.yield, bind_map_left, bind_pure_comp]
160163
rfl
161164
| .skip it' h => rfl
162165
| .done h => rfl
@@ -178,7 +181,7 @@ theorem IterM.step_filterMap [Monad m] [LawfulMonad m] {f : β → Option β'} :
178181
apply bind_congr
179182
intro step
180183
split
181-
· simp only [pure_bind]
184+
· simp only [PostconditionT.pure, PlausibleIterStep.skip, PlausibleIterStep.yield, pure_bind]
182185
split <;> split <;> simp_all
183186
· simp
184187
· simp
@@ -229,7 +232,6 @@ end Step
229232

230233
section Lawful
231234

232-
233235
instance {α β γ : Type w} {m : Type w → Type w'} {n : Type w → Type w''} {o : Type w → Type x}
234236
[Monad m] [Monad n] [Monad o] [LawfulMonad n] [LawfulMonad o] [Iterator α m β] [Finite α m]
235237
[IteratorCollect α m o] [LawfulIteratorCollect α m o]
@@ -408,4 +410,195 @@ theorem IterM.toArray_filter {α : Type w} {m : Type w → Type w'} [Monad m] [L
408410

409411
end ToArray
410412

413+
section Equivalence
414+
415+
theorem stepAsHetT_filterMapWithPostcondition [Monad m] [LawfulMonad m] [Monad n]
416+
[LawfulMonad n] [Iterator α m β] [MonadLiftT m n] [LawfulMonadLiftT m n]
417+
{f : β → PostconditionT n (Option γ)} {it : IterM (α := α) m β} :
418+
(IterM.stepAsHetT (it.filterMapWithPostcondition f)) =
419+
(((IterM.stepAsHetT it).liftInner n : HetT n _)).bind (match · with
420+
| .yield it' out => do match ← HetT.ofPostconditionT (f out) with
421+
| some out' => return .yield (it'.filterMapWithPostcondition f) out'
422+
| none => return .skip (it'.filterMapWithPostcondition f)
423+
| .skip it' => return .skip (it'.filterMapWithPostcondition f)
424+
| .done => return .done) := by
425+
simp only [HetT.ext_iff, Equivalence.property_step, Equivalence.prun_step, HetT.prun_bind,
426+
HetT.property_liftInner, Equivalence.prun_liftInner_step, HetT.property_bind]
427+
refine ⟨?_, ?_⟩
428+
· ext step
429+
constructor
430+
· intro h
431+
cases h
432+
case yieldNone it' out h h' =>
433+
refine ⟨_, h, ?_⟩
434+
simp only [bind, HetT.property_bind, HetT.property_ofPostconditionT]
435+
exact ⟨none, by simp [Pure.pure]; exact ⟨h', rfl⟩⟩
436+
case yieldSome it' out out' h h' =>
437+
refine ⟨_, h, ?_⟩
438+
simp only [bind, HetT.property_bind, HetT.property_ofPostconditionT]
439+
exact ⟨some out', by simp [Pure.pure]; exact ⟨h', rfl⟩⟩
440+
case skip it' h =>
441+
exact ⟨_, h, by simp [Pure.pure]; rfl⟩
442+
case done h =>
443+
exact ⟨_, h, by simp [Pure.pure]⟩
444+
· rintro ⟨step', h, h'⟩
445+
cases step'
446+
case yield it' out =>
447+
simp only [bind, HetT.property_bind, HetT.property_ofPostconditionT] at h'
448+
rcases h' with ⟨out', h'⟩
449+
cases out'
450+
· simp only [pure, HetT.property_pure] at h'
451+
cases h'.2
452+
exact .yieldNone h h'.1
453+
· simp only [pure, HetT.property_pure] at h'
454+
cases h'.2
455+
exact .yieldSome h h'.1
456+
case skip it' =>
457+
simp only [pure, HetT.property_pure] at h'
458+
cases h'
459+
exact .skip h
460+
case done =>
461+
simp only [pure, HetT.property_pure] at h'
462+
cases h'
463+
exact .done h
464+
· intro β f
465+
simp only [IterM.step_filterMapWithPostcondition, PlausibleIterStep.skip,
466+
PlausibleIterStep.yield, PlausibleIterStep.done, bind_assoc]
467+
apply bind_congr
468+
intro step
469+
cases step using PlausibleIterStep.casesOn
470+
· simp only [bind_assoc, bind, HetT.prun_bind, HetT.property_ofPostconditionT,
471+
HetT.prun_ofPostconditionT]
472+
apply bind_congr
473+
rintro ⟨out, _⟩
474+
cases out <;> simp [pure]
475+
· simp [pure]
476+
· simp [pure]
477+
478+
theorem IterM.Equiv.filterMapWithPostcondition {α₁ α₂ β γ : Type w}
479+
{m : Type w → Type w'} {n : Type w → Type w''}
480+
[Monad m] [LawfulMonad m] [Monad n] [LawfulMonad n] [Iterator α₁ m β] [Iterator α₂ m β]
481+
[MonadLiftT m n] [LawfulMonadLiftT m n]
482+
{f : β → PostconditionT n (Option γ)} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
483+
(h : IterM.Equiv ita itb) :
484+
IterM.Equiv (ita.filterMapWithPostcondition f) (itb.filterMapWithPostcondition f) := by
485+
rw [IterM.Equiv]
486+
refine BundledIterM.Equiv.fixpoint_induct n γ ?R ?implies (.ofIterM _) (.ofIterM _) ?hR
487+
case R =>
488+
intro ita' itb'
489+
exact ∃ (ita : IterM (α := α₁) m β) (itb : IterM (α := α₂) m β),
490+
ita' = .ofIterM (ita.filterMapWithPostcondition f) ∧
491+
itb' = .ofIterM (itb.filterMapWithPostcondition f) ∧
492+
IterM.Equiv ita itb
493+
case hR =>
494+
exact ⟨_, _, rfl, rfl, h⟩
495+
case implies =>
496+
rintro _ _ ⟨ita, itb, rfl, rfl, h'⟩
497+
replace h := h'
498+
simp only [BundledIterM.step, BundledIterM.iterator_ofIterM, HetT.map_eq_pure_bind,
499+
HetT.bind_assoc, Function.comp_apply, HetT.pure_bind, IterStep.mapIterator_mapIterator]
500+
rw [stepAsHetT_filterMapWithPostcondition, stepAsHetT_filterMapWithPostcondition]
501+
simp only [HetT.bind_assoc]
502+
simp only [Equiv, BundledIterM.Equiv, BundledIterM.step, BundledIterM.iterator_ofIterM,
503+
HetT.map_eq_pure_bind, HetT.bind_assoc, Function.comp_apply, HetT.pure_bind,
504+
IterStep.mapIterator_mapIterator] at h'
505+
apply liftInner_stepAsHetT_bind_congr h
506+
intro sa hsa sb hsb hs
507+
simp only [IterStep.bundledQuotient] at hs
508+
cases sa <;> cases sb <;> (try exfalso; simp_all; done)
509+
case yield =>
510+
simp only [IterStep.mapIterator_yield, Function.comp_apply, IterStep.yield.injEq,
511+
BundledIterM.Equiv.quotMk_eq_iff] at hs
512+
cases hs.2
513+
simp only [bind, HetT.bind_assoc]
514+
congr
515+
ext out
516+
cases out
517+
all_goals
518+
simp only [pure, HetT.pure_bind, IterStep.mapIterator_skip, IterStep.mapIterator_yield,
519+
Function.comp_apply]
520+
congr 2
521+
apply Quot.sound
522+
exact ⟨_, _, rfl, rfl, hs.1
523+
case skip =>
524+
simp only [IterStep.mapIterator_skip, Function.comp_apply, IterStep.skip.injEq,
525+
BundledIterM.Equiv.quotMk_eq_iff] at hs
526+
simp only [pure, HetT.pure_bind, IterStep.mapIterator_skip, Function.comp_apply]
527+
congr 2
528+
apply Quot.sound
529+
exact ⟨_, _, rfl, rfl, hs⟩
530+
case done =>
531+
simp [Pure.pure]
532+
533+
theorem IterM.Equiv.filterWithPostcondition {α₁ α₂ β : Type w}
534+
{m : Type w → Type w'} {n : Type w → Type w''} [Monad m] [LawfulMonad m]
535+
[Monad n] [LawfulMonad n] [Iterator α₁ m β] [Iterator α₂ m β]
536+
[MonadLiftT m n] [LawfulMonadLiftT m n]
537+
{f : β → PostconditionT n (ULift Bool)} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
538+
(h : IterM.Equiv ita itb) :
539+
IterM.Equiv (ita.filterWithPostcondition f) (itb.filterWithPostcondition f) :=
540+
IterM.Equiv.filterMapWithPostcondition h
541+
542+
theorem IterM.Equiv.mapWithPostcondition {α₁ α₂ β γ : Type w}
543+
{m : Type w → Type w'} {n : Type w → Type w''} [Monad m] [LawfulMonad m]
544+
[Monad n] [LawfulMonad n] [Iterator α₁ m β] [Iterator α₂ m β]
545+
[MonadLiftT m n] [LawfulMonadLiftT m n]
546+
{f : β → PostconditionT n γ} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
547+
(h : IterM.Equiv ita itb) :
548+
IterM.Equiv (ita.mapWithPostcondition f) (itb.mapWithPostcondition f) :=
549+
IterM.Equiv.filterMapWithPostcondition h
550+
551+
theorem IterM.Equiv.filterMapM {α₁ α₂ β γ : Type w}
552+
{m : Type w → Type w'} {n : Type w → Type w''} [Monad m] [LawfulMonad m]
553+
[Monad n] [LawfulMonad n] [Iterator α₁ m β] [Iterator α₂ m β]
554+
[MonadLiftT m n] [LawfulMonadLiftT m n]
555+
{f : β → n (Option γ)} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
556+
(h : IterM.Equiv ita itb) :
557+
IterM.Equiv (ita.filterMapM f) (itb.filterMapM f) :=
558+
IterM.Equiv.filterMapWithPostcondition h
559+
560+
theorem IterM.Equiv.filterM {α₁ α₂ β : Type w}
561+
{m : Type w → Type w'} {n : Type w → Type w''} [Monad m] [LawfulMonad m]
562+
[Monad n] [LawfulMonad n] [Iterator α₁ m β] [Iterator α₂ m β]
563+
[MonadLiftT m n] [LawfulMonadLiftT m n]
564+
{f : β → n (ULift Bool)} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
565+
(h : IterM.Equiv ita itb) :
566+
IterM.Equiv (ita.filterM f) (itb.filterM f) :=
567+
IterM.Equiv.filterMapWithPostcondition h
568+
569+
theorem IterM.Equiv.mapM {α₁ α₂ β γ : Type w}
570+
{m : Type w → Type w'} {n : Type w → Type w''} [Monad m] [LawfulMonad m]
571+
[Monad n] [LawfulMonad n] [Iterator α₁ m β] [Iterator α₂ m β]
572+
[MonadLiftT m n] [LawfulMonadLiftT m n]
573+
{f : β → n γ} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
574+
(h : IterM.Equiv ita itb) :
575+
IterM.Equiv (ita.mapM f) (itb.mapM f) :=
576+
IterM.Equiv.filterMapWithPostcondition h
577+
578+
theorem IterM.Equiv.filterMap {α₁ α₂ β γ : Type w}
579+
{m : Type w → Type w'} [Monad m] [LawfulMonad m]
580+
[Iterator α₁ m β] [Iterator α₂ m β]
581+
{f : β → Option γ} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
582+
(h : IterM.Equiv ita itb) :
583+
IterM.Equiv (ita.filterMap f) (itb.filterMap f) :=
584+
IterM.Equiv.filterMapWithPostcondition h
585+
586+
theorem IterM.Equiv.filter {α₁ α₂ β : Type w}
587+
{m : Type w → Type w'} [Monad m] [LawfulMonad m]
588+
[Iterator α₁ m β] [Iterator α₂ m β]
589+
{f : β → Bool} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
590+
(h : IterM.Equiv ita itb) :
591+
IterM.Equiv (ita.filter f) (itb.filter f) :=
592+
IterM.Equiv.filterMapWithPostcondition h
593+
594+
theorem IterM.Equiv.map {α₁ α₂ β γ : Type w}
595+
{m : Type w → Type w'} [Monad m] [LawfulMonad m]
596+
[Iterator α₁ m β] [Iterator α₂ m β]
597+
{f : β → γ} {ita : IterM (α := α₁) m β} {itb : IterM (α := α₂) m β}
598+
(h : IterM.Equiv ita itb) :
599+
IterM.Equiv (ita.map f) (itb.map f) :=
600+
IterM.Equiv.filterMapWithPostcondition h
601+
602+
end Equivalence
603+
411604
end Std.Iterators

src/Std/Data/Iterators/Lemmas/Consumers/Collect.lean

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ theorem Iter.toArray_toList {α β} [Iterator α Id β] [Finite α Id] [Iterator
4848
it.toList.toArray = it.toArray := by
4949
simp [toArray_eq_toArray_toIterM, toList_eq_toList_toIterM, ← IterM.toArray_toList]
5050

51+
@[simp]
52+
theorem Iter.reverse_toListRev [Iterator α Id β] [Finite α Id]
53+
[IteratorCollect α Id Id] [LawfulIteratorCollect α Id Id]
54+
{it : Iter (α := α) β} :
55+
it.toListRev.reverse = it.toList := by
56+
simp [toListRev_eq_toListRev_toIterM, toList_eq_toList_toIterM, ← IterM.reverse_toListRev]
57+
5158
theorem Iter.toListRev_eq {α β} [Iterator α Id β] [Finite α Id] [IteratorCollect α Id Id]
5259
[LawfulIteratorCollect α Id Id] {it : Iter (α := α) β} :
5360
it.toListRev = it.toList.reverse := by
@@ -102,4 +109,30 @@ theorem Iter.toList_eq_of_atIdxSlow?_eq {α₁ α₂ β}
102109
it₁.toList = it₂.toList := by
103110
ext; simp [getElem?_toList_eq_atIdxSlow?, h]
104111

112+
section Equivalence
113+
114+
theorem Iter.Equiv.toListRev_eq
115+
[Iterator α₁ Id β] [Iterator α₂ Id β] [Finite α₁ Id] [Finite α₂ Id]
116+
{ita : Iter (α := α₁) β} {itb : Iter (α := α₂) β} (h : Iter.Equiv ita itb) :
117+
ita.toListRev = itb.toListRev := by
118+
simp [Iter.toListRev_eq_toListRev_toIterM, h.toIterM.toListRev_eq]
119+
120+
theorem Iter.Equiv.toList_eq
121+
[Iterator α₁ Id β] [Iterator α₂ Id β] [Finite α₁ Id] [Finite α₂ Id]
122+
[IteratorCollect α₁ Id Id] [LawfulIteratorCollect α₁ Id Id]
123+
[IteratorCollect α₂ Id Id] [LawfulIteratorCollect α₂ Id Id]
124+
{ita : Iter (α := α₁) β} {itb : Iter (α := α₂) β} (h : Iter.Equiv ita itb) :
125+
ita.toList = itb.toList := by
126+
simp only [← Iter.reverse_toListRev, toListRev_eq h]
127+
128+
theorem Iter.Equiv.toArray_eq
129+
[Iterator α₁ Id β] [Iterator α₂ Id β] [Finite α₁ Id] [Finite α₂ Id]
130+
[IteratorCollect α₁ Id Id] [LawfulIteratorCollect α₁ Id Id]
131+
[IteratorCollect α₂ Id Id] [LawfulIteratorCollect α₂ Id Id]
132+
{ita : Iter (α := α₁) β} {itb : Iter (α := α₂) β} (h : Iter.Equiv ita itb) :
133+
ita.toArray = itb.toArray := by
134+
simp only [← Iter.toArray_toList, toList_eq h]
135+
136+
end Equivalence
137+
105138
end Std.Iterators

0 commit comments

Comments
 (0)