@@ -3,6 +3,7 @@ Copyright (c) 2022 Mac Malone. All rights reserved.
3
3
Released under Apache 2.0 license as described in the file LICENSE.
4
4
Authors: Mac Malone
5
5
-/
6
+ import Lake.Util.Cycle
6
7
import Lake.Util.Store
7
8
import Lake.Util.EquipT
8
9
@@ -19,53 +20,116 @@ This is called a suspending scheduler in *Build systems à la carte*.
19
20
open Std
20
21
namespace Lake
21
22
22
- /-! ## Abstractions -/
23
-
24
- /-- A dependently typed object builder. -/
25
- abbrev DBuildFn. {u,v,w} (ι : Type u) (β : ι → Type v) (m : Type v → Type w) :=
26
- (i : ι) → m (β i)
27
-
28
- /-- A dependently typed recursive object builder. -/
29
- abbrev DRecBuildFn. {u,v,w} (ι : Type u) (β : ι → Type v) (m : Type v → Type w) :=
30
- (i : ι) → EquipT (DBuildFn ι β m) m (β i)
31
-
32
- /-- A recursive object builder. -/
33
- abbrev RecBuildFn ι α m := DRecBuildFn ι (fun _ => α) m
34
-
35
- /-- `ExceptT` for build cycles. -/
36
- abbrev CycleT (κ) :=
37
- ExceptT (List κ)
38
-
39
- /-! ## Algorithm -/
40
-
41
- /-- Auxiliary function for `buildTop`. -/
42
- @[specialize] partial def buildTopCore [BEq κ] [Monad m] [MonadDStore κ β m]
43
- (parents : List κ) (keyOf : ι → κ) (build : DRecBuildFn ι (β ∘ keyOf) (CycleT κ m))
44
- (info : ι) : CycleT κ m (β (keyOf info)) := do
45
- let key := keyOf info
46
- -- return previous output if already built
47
- if let some output ← fetch? key then
48
- return output
49
- -- detect cyclic builds
50
- if parents.contains key then
51
- throw <| key :: (parents.partition (· != key)).1 ++ [key]
52
- -- build the key recursively
53
- let output ← build info <| buildTopCore (key :: parents) keyOf build
54
- -- save the output (to prevent repeated builds of the same key)
55
- store key output
56
- return output
23
+ /-!
24
+ ## Recursive Fetching
25
+
26
+ In this section, we define the primitives that make up a builder.
27
+ -/
28
+
29
+ /--
30
+ A dependently typed monadic *fetch* function.
31
+
32
+ That is, a function within the monad `m` and takes an input `a : α`
33
+ describing what to fetch and and produces some output `b : β a` (dependently
34
+ typed) or `b : B` (not) describing what was fetched. All build functions are
35
+ fetch functions, but not all fetch functions need build something.
36
+ -/
37
+ abbrev DFetchFn (α : Type u) (β : α → Type v) (m : Type v → Type w) :=
38
+ (a : α) → m (β a)
39
+
40
+ /-!
41
+ In order to nest builds / fetches within one another,
42
+ we equip the monad `m` with a fetch function of its own.
43
+ -/
44
+
45
+ /-- A transformer that equips a monad with a `DFetchFn`. -/
46
+ abbrev DFetchT (α : Type u) (β : α → Type v) (m : Type v → Type w) :=
47
+ EquipT (DFetchFn α β m) m
48
+
49
+ /-- A `DFetchT` that is not dependently typed. -/
50
+ abbrev FetchT (α : Type u) (β : Type v) (m : Type v → Type w) :=
51
+ DFetchT α (fun _ => β) m
52
+
53
+ /-!
54
+ We can then use the such a monad as the basis for a fetch function itself.
55
+ -/
56
+
57
+ /-
58
+ A `DFetchFn` that utilizes another `DFetchFn` equipped to the monad to
59
+ fetch values. It is thus usually implemented recursively via some variation
60
+ of the `recFetch` function below, hence the "rec" in both names.
61
+ -/
62
+ abbrev DRecFetchFn (α : Type u) (β : α → Type v) (m : Type v → Type w) :=
63
+ DFetchFn α β (DFetchT α β m)
64
+
65
+ /-- A `DRecFetchFn` that is not dependently typed. -/
66
+ abbrev RecFetchFn (α : Type u) (β : Type v) (m : Type v → Type w) :=
67
+ α → FetchT α β m β
68
+
69
+ /-- A `DFetchFn` that provides its base `DRecFetchFn` with itself. -/
70
+ @[specialize] partial def recFetch
71
+ [(α : Type u) → Nonempty (m α)] (fetch : DRecFetchFn α β m) : DFetchFn α β m :=
72
+ fun a => fetch a (recFetch fetch)
73
+
74
+ /-!
75
+ The basic `recFetch` can fail to terminate in a variety of ways,
76
+ it can even cycle (i.e., `a` fetches `b` which fetches `a`). Thus, we
77
+ define the `acyclicRecFetch` below to guard against such cases.
78
+ -/
79
+
80
+ /--
81
+ A `recFetch` augmented by a `CycleT` to guard against recursive cycles.
82
+ If the set of visited keys is finite, this function should provably terminate.
83
+
84
+ We use `keyOf` to the derive the unique key of a fetch from its descriptor
85
+ `a : α`. We do this because descriptors may not be comparable and/or contain
86
+ more information than necessary to determine uniqueness.
87
+ -/
88
+ @[inline] partial def acyclicRecFetch [BEq κ] [Monad m]
89
+ (keyOf : α → κ) (fetch : DRecFetchFn α β (CycleT κ m)) : DFetchFn α β (CycleT κ m) :=
90
+ recFetch fun a recurse =>
91
+ /-
92
+ NOTE: We provide the stack directly to `recurse` rather than
93
+ get it through `ReaderT` to prevent it being overridden by the `fetch`
94
+ function (and thereby potentially produce a cycle).
95
+ -/
96
+ guardCycle (keyOf a) fun stack => fetch a (recurse · stack) stack
97
+
98
+ /-!
99
+ When building, we usually do not want to build the same thing twice during
100
+ a single build pass. At the same time, separate builds may both wish to fetch
101
+ the same thing. Thus, we need to keep track of past builds and make there
102
+ results avoid to future fetches. This is what `memoizedRecFetch` below does.
103
+ -/
104
+
105
+ /--
106
+ An `acyclicRecFetch` augmented with a `MonadDStore` to
107
+ memoize fetch results and thus avoid computing the same result twice.
108
+ -/
109
+ @[inline] def memoizedRecFetch [BEq κ] [Monad m] [MonadDStore κ β m]
110
+ (keyOf : α → κ) (fetch : DRecFetchFn α (fun a => β (keyOf a)) (CycleT κ m))
111
+ : DFetchFn α (fun a => β (keyOf a)) (CycleT κ m) :=
112
+ acyclicRecFetch keyOf fun a recurse =>
113
+ fetchOrCreate (keyOf a) do fetch a recurse
114
+
115
+ /-!
116
+ ## Building
117
+
118
+ In this section, we use the abstractions we have just created to define
119
+ the desired topological recursive build function (a.k.a. a suspending scheduler).
120
+ -/
57
121
58
122
/-- Dependently typed version of `buildTop`. -/
59
- @[inline ] def buildDTop (β) [BEq κ] [Monad m] [MonadDStore κ β m]
60
- (keyOf : ι → κ) (build : DRecBuildFn ι (β ∘ keyOf) (CycleT κ m))
61
- (info : ι) : CycleT κ m (β (keyOf info )) :=
62
- buildTopCore [] keyOf build info
123
+ @[specialize ] def buildDTop (β) [BEq κ] [Monad m] [MonadDStore κ β m]
124
+ (keyOf : α → κ) (a : α) ( build : DRecFetchFn α ( fun a => β ( keyOf a) ) (CycleT κ m))
125
+ : ExceptT (Cycle κ) m (β (keyOf a )) :=
126
+ memoizedRecFetch keyOf build a []
63
127
64
128
/--
65
129
Recursively fills a `MonadStore` of key-object pairs by
66
130
building objects topologically (ι.e., via a depth-first search with memoization).
67
131
If a cycle is detected, the list of keys traversed is thrown.
68
132
-/
69
- @[inline ] def buildTop [BEq κ] [Monad m] [MonadStore κ α m]
70
- (keyOf : ι → κ) (build : RecBuildFn ι α (CycleT κ m)) (info : ι) : CycleT κ m α :=
71
- buildDTop ( fun _ => α ) keyOf build info
133
+ @[specialize ] def buildTop [BEq κ] [Monad m] [MonadStore κ β m]
134
+ (keyOf : α → κ) (a : α) (build : RecFetchFn α β (CycleT κ m)) : ExceptT (Cycle κ) m β :=
135
+ memoizedRecFetch (β := fun _ => β ) keyOf build a []
0 commit comments