Skip to content
This repository was archived by the owner on Oct 25, 2023. It is now read-only.

Commit 8c623a3

Browse files
committed
refactor: Lake.Build.Topological tweaks + docs
1 parent 8322e8d commit 8c623a3

File tree

7 files changed

+149
-45
lines changed

7 files changed

+149
-45
lines changed

Diff for: Lake/Build/Context.lean

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ abbrev BuildM := BuildT <| MonadLogT BaseIO OptionIO
3131
/-- A transformer to equip a monad with a Lake build store. -/
3232
abbrev BuildStoreT := StateT BuildStore
3333

34+
/-- A Lake build cycle. -/
35+
abbrev BuildCycle := Cycle BuildKey
36+
3437
/-- A transformer for monads that may encounter a build cycle. -/
3538
abbrev BuildCycleT := CycleT BuildKey
3639

Diff for: Lake/Build/Index.lean

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Recursively build the given info using the Lake build index
7070
and a topological / suspending scheduler.
7171
-/
7272
def buildIndexTop' (info : BuildInfo) : RecBuildM (BuildData info.key) :=
73-
buildDTop BuildData BuildInfo.key recBuildWithIndex info
73+
buildDTop BuildData BuildInfo.key info recBuildWithIndex
7474

7575
/--
7676
Recursively build the given info using the Lake build index

Diff for: Lake/Build/Monad.lean

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Run the recursive build in the given build store.
3636
If a cycle is encountered, log it and then fail.
3737
-/
3838
@[inline] def RecBuildM.runIn (store : BuildStore) (build : RecBuildM α) : BuildM (α × BuildStore) := do
39-
let (res, store) ← EStateT.run store build
39+
let (res, store) ← EStateT.run store <| ReaderT.run build []
4040
return (← failOnBuildCycle res, store)
4141

4242
/--

Diff for: Lake/Build/Topological.lean

+106-42
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Copyright (c) 2022 Mac Malone. All rights reserved.
33
Released under Apache 2.0 license as described in the file LICENSE.
44
Authors: Mac Malone
55
-/
6+
import Lake.Util.Cycle
67
import Lake.Util.Store
78
import Lake.Util.EquipT
89

@@ -19,53 +20,116 @@ This is called a suspending scheduler in *Build systems à la carte*.
1920
open Std
2021
namespace Lake
2122

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+
-/
57121

58122
/-- 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 []
63127

64128
/--
65129
Recursively fills a `MonadStore` of key-object pairs by
66130
building objects topologically (ι.e., via a depth-first search with memoization).
67131
If a cycle is detected, the list of keys traversed is thrown.
68132
-/
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 []

Diff for: Lake/Load/Main.lean

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def resolveDeps (ws : Workspace) (pkg : Package) (leanOpts : Options)
4444
store := fun _ pkg => modify (·.addPackage pkg)
4545
}
4646
let (res, ws) ← EStateT.run ws <| deps.mapM fun dep =>
47-
buildTop (·.2.name) recResolveDep (pkg, dep)
47+
buildTop (·.2.name) (pkg, dep) recResolveDep
4848
match res with
4949
| Except.ok deps => return (ws, deps)
5050
| Except.error cycle => do

Diff for: Lake/Util/Cycle.lean

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/-
2+
Copyright (c) 2022 Mac Malone. All rights reserved.
3+
Released under Apache 2.0 license as described in the file LICENSE.
4+
Authors: Mac Malone
5+
-/
6+
7+
namespace Lake
8+
9+
/-- A sequence of calls donated by the key type `κ`. -/
10+
abbrev CallStack κ := List κ
11+
12+
/-- A `CallStack` ending in a cycle. -/
13+
abbrev Cycle κ := CallStack κ
14+
15+
/-- A transformer that equips a monad with a `CallStack` to detect cycles. -/
16+
abbrev CycleT κ m := ReaderT (CallStack κ) <| ExceptT (Cycle κ) m
17+
18+
/--
19+
Add `key` to the monad's `CallStack` before invoking `act`.
20+
If adding `key` produces a cycle, the cyclic call stack is thrown.
21+
-/
22+
@[inline] def guardCycle [BEq κ] [Monad m]
23+
(key : κ) (act : CycleT κ m α) : CycleT κ m α := do
24+
let parents ← read
25+
if parents.contains key then
26+
throw <| key :: (parents.partition (· != key)).1 ++ [key]
27+
else
28+
act (key :: parents)

Diff for: Lake/Util/Store.lean

+9
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,12 @@ abbrev MonadStore κ α m := MonadDStore κ (fun _ => α) m
2727
instance [MonadLift m n] [MonadDStore κ β m] : MonadDStore κ β n where
2828
fetch? k := liftM (m := m) <| fetch? k
2929
store k a := liftM (m := m) <| store k a
30+
31+
@[inline] def fetchOrCreate [Monad m]
32+
(key : κ) [MonadStore1 key α m] (create : m α) : m α := do
33+
if let some val ← fetch? key then
34+
return val
35+
else
36+
let val ← create
37+
store key val
38+
return val

0 commit comments

Comments
 (0)