Skip to content

Commit c059daf

Browse files
committed
[ docs ] Compiling T10
1 parent 4774dbc commit c059daf

1 file changed

Lines changed: 111 additions & 93 deletions

File tree

docs/source/tutorials/t10-generating-gadts-with-proofs.md

Lines changed: 111 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -17,104 +17,94 @@ By the end, you will:
1717

1818
- Completion of [Tutorial 5: DeriveGen Signatures](t05-derivegen-signatures.md)
1919
- Understanding of dependent pairs and indexed types
20+
- Idris2 source file `./src/Playground.idr` with the header:
2021

21-
---
22+
```idris
23+
import Deriving.DepTyCheck.Gen
24+
import System.Random.Pure.StdGen
25+
```
2226

2327
## Step 1: Define a SortedList Type
2428

2529
First, let's define a list type that is guaranteed to be sorted. The key is that the `SCons` constructor requires a proof that adding a new element maintains sortedness.
2630

27-
1. **Create a new file** named `SortedListTutorial.idr`.
28-
29-
2. **Add the following code.**
30-
31-
```idris
32-
module SortedListTutorial
33-
34-
import Deriving.DepTyCheck.Gen
35-
import Data.Fuel
36-
37-
import Test.DepTyCheck.Gen
38-
import Test.DepTyCheck.Runner
39-
import System.Random.Pure.StdGen
40-
41-
data SortedList : Type where
42-
SNil : SortedList
43-
SCons : (x : Nat) -> (xs : SortedList) -> {auto prf : isSorted (x :: toList xs)} -> SortedList
44-
45-
toList : SortedList -> List Nat
46-
toList SNil = []
47-
toList (SCons x xs prf) = x :: toList xs
48-
49-
isSorted : List Nat -> Bool
50-
isSorted [] = True
51-
isSorted [_] = True
52-
isSorted (x :: y :: xs) = x <= y && isSorted (y :: xs)
53-
```
31+
```idris
32+
isSorted : List Nat -> Bool
33+
isSorted [] = True
34+
isSorted [_] = True
35+
isSorted (x :: y :: xs) = x <= y && isSorted (y :: xs)
36+
37+
mutual
38+
data SortedList : Type where
39+
SNil : SortedList
40+
SCons : (x : Nat) -> (xs : SortedList) -> {auto prf : So $ isSorted (x :: toList xs)} -> SortedList
41+
42+
toList : SortedList -> List Nat
43+
toList SNil = []
44+
toList (SCons x xs) = x :: toList xs
45+
```
5446

55-
The `SCons` constructor has an **auto-implicit** argument `{auto prf : isSorted (x :: toList xs)}`. This means:
56-
- To construct an `SCons`, Idris must find a proof that the list is sorted.
57-
- The `auto` keyword tells Idris to search for this proof automatically.
47+
The `SCons` constructor has an **auto-implicit** argument `{auto prf : isSorted (x :: toList xs)}`. This means:
48+
- To construct an `SCons`, Idris must find a proof that the list is sorted.
49+
- The `auto` keyword tells Idris to search for this proof automatically.
5850

5951
---
6052

6153
## Step 2: Derive the Generator
6254

6355
Now comes the magic. We derive a generator for `SortedList` with a single line.
6456

65-
1. **Add the derived generator.**
57+
### Add the derived generator
6658

67-
```idris
68-
genSortedList : Fuel -> Gen MaybeEmpty SortedList
69-
genSortedList = deriveGen
70-
```
59+
```idris
60+
genSortedList : Fuel -> Gen MaybeEmpty SortedList
61+
genSortedList = deriveGen
62+
```
7163

72-
That's it! No manual logic, no special handling for the proof argument. `deriveGen` will:
73-
1. Generate a candidate `x : Nat`
74-
2. Recursively generate `xs : SortedList`
75-
3. Check if `isSorted (x :: toList xs)` can be proven
76-
4. If yes, construct the `SCons`; if no, try another `x`
64+
That's it! No manual logic, no special handling for the proof argument. `deriveGen` will:
65+
1. Generate a candidate `x : Nat`
66+
2. Recursively generate `xs : SortedList`
67+
3. Check if `isSorted (x :: toList xs)` can be proven
68+
4. If yes, construct the `SCons`; if no, try another `x`
7769

7870
---
7971

8072
## Step 3: Run and Verify
8173

8274
Let's test the generator and verify that all output is sorted.
8375

84-
1. **Add a `main` function.**
85-
86-
```idris
87-
main : IO ()
88-
main = do
89-
putStrLn "--- Generating 5 sorted lists ---"
90-
replicate 5 $ do
91-
Just sl <- pick1 (genSortedList (limit 20))
92-
| Nothing => printLn "Generation failed"
93-
let asList = toList sl
94-
putStrLn $ "Generated: " ++ show asList
95-
putStrLn $ "Is sorted: " ++ show (isSorted asList)
96-
```
76+
```idris
77+
testList : HasIO io => io ()
78+
testList = do
79+
putStrLn "--- Generating 5 sorted lists ---"
80+
for_ (the (List Int) [1..5]) $ \_ => do
81+
Just sl <- pick (genSortedList (limit 5))
82+
| Nothing => printLn "Generation failed"
83+
let asList = toList sl
84+
putStrLn $ "Generated: " ++ show asList
85+
putStrLn $ "Is sorted: " ++ show (isSorted asList)
86+
```
9787

98-
2. **Compile and run.**
88+
### Compile and run
9989

10090
```bash
101-
idris2 --build SortedListTutorial.idr
102-
./build/exec/SortedListTutorial
91+
echo -e ':exec testList' | rlwrap pack repl ./src/Playground.idr
10392
```
10493

105-
3. **Analyze the output.**
94+
### Analyze the output
10695

10796
```
108-
--- Generating 5 sorted lists ---
109-
Generated: [0, 3, 5, 12, 42]
97+
...
98+
Main> --- Generating 5 sorted lists ---
99+
Generated: [2, 3, 3, 4, 4]
110100
Is sorted: True
111-
Generated: [8, 8, 9, 15, 21, 33]
101+
Generated: [2, 2]
112102
Is sorted: True
113-
Generated: []
103+
Generated: [3, 4]
114104
Is sorted: True
115-
Generated: [2, 7, 19]
105+
Generated: [2]
116106
Is sorted: True
117-
Generated: [1, 1, 4, 10, 10, 15]
107+
Generated: [4]
118108
Is sorted: True
119109
```
120110

@@ -126,7 +116,7 @@ Let's test the generator and verify that all output is sorted.
126116

127117
How does `deriveGen` solve the proof constraint? The key is in the **search order** and **backtracking**.
128118

129-
When `deriveGen` encounters `{auto prf : isSorted (x :: toList xs)}`, it:
119+
When `deriveGen` encounters `{auto prf : So $ isSorted (x :: toList xs)}`, it:
130120

131121
1. **Generates candidates** for `x` from the default `Nat` generator
132122
2. **Recursively generates** `xs : SortedList` (which is already sorted by construction)
@@ -135,30 +125,39 @@ When `deriveGen` encounters `{auto prf : isSorted (x :: toList xs)}`, it:
135125

136126
This is why the generator may be slower for complex constraints — it may need multiple attempts to find valid values.
137127

138-
### The Role of Fuel
139-
140-
The `Fuel` parameter controls how deep the recursion can go. With more fuel, `deriveGen` can generate longer sorted lists:
128+
The `Fuel` parameter controls how deep the recursion can go, so, with more fuel, `deriveGen` can generate longer sorted lists:
141129

142130
```idris
143131
-- Add to main:
144-
putStrLn "\n--- With small fuel (limit 3) ---"
145-
Just sl1 <- pick1 (genSortedList (limit 3))
146-
| Nothing => printLn "Failed"
147-
printLn $ toList sl1
148-
149-
putStrLn "\n--- With large fuel (limit 10) ---"
150-
Just sl2 <- pick1 (genSortedList (limit 10))
151-
| Nothing => printLn "Failed"
152-
printLn $ toList sl2
132+
testFuel : IO ()
133+
testFuel = do
134+
putStrLn "\n--- With small fuel (limit 3) ---"
135+
Just sl1 <- pick (genSortedList (limit 3))
136+
| Nothing => printLn "Generation failed"
137+
printLn $ toList sl1
138+
139+
putStrLn "\n--- With large fuel (limit 8) ---"
140+
Just sl2 <- pick (genSortedList (limit 8))
141+
| Nothing => printLn "Generation failed"
142+
printLn $ toList sl2
153143
```
154144

155-
Output:
156-
```
157-
--- With small fuel (limit 3) ---
158-
[2, 5, 8]
159-
160-
--- With large fuel (limit 10) ---
161-
[0, 1, 3, 7, 12, 15, 20, 25, 30, 42]
145+
Run it:
146+
147+
```bash
148+
echo -e ':exec testFuel' | rlwrap pack repl ./src/Playground.idr
149+
...
150+
Main> --- Generating 5 sorted lists ---
151+
Generated: [2, 3, 3, 4, 4]
152+
Is sorted: True
153+
Generated: [2, 2]
154+
Is sorted: True
155+
Generated: [3, 4]
156+
Is sorted: True
157+
Generated: [2]
158+
Is sorted: True
159+
Generated: [4]
160+
Is sorted: True
162161
```
163162

164163
---
@@ -168,24 +167,35 @@ Output:
168167
Let's see another example of proof-carrying data. Consider a type that represents numbers bounded by a given limit:
169168

170169
```idris
171-
-- Add to SortedListTutorial.idr
172-
173-
data BoundedNat : (limit : Nat) -> Type where
174-
MkBounded : (n : Nat) -> {auto prf : n `LT` limit} -> BoundedNat limit
170+
data BoundedNat : (limit' : Nat) -> Type where
171+
MkBounded : (n : Nat) -> {auto prf : n `LT` limit'} -> BoundedNat limit'
175172
176-
genBoundedNat : (limit : Nat) -> Fuel -> Gen MaybeEmpty (BoundedNat limit)
177-
genBoundedNat limit = deriveGen
173+
genBoundedNat : Fuel -> (limit' : Nat) -> Gen MaybeEmpty (BoundedNat limit')
174+
genBoundedNat = deriveGen
178175
179176
testBounded : IO ()
180177
testBounded = do
181-
putStrLn "--- Generating BoundedNat with limit 5 ---"
182-
replicate 5 $ do
183-
Just bn <- pick1 (genBoundedNat 5 (limit 10))
178+
putStrLn "--- Generating BoundedNat with limit'=5 ---"
179+
for_ (the (List Int) [1..5]) $ \_ => do
180+
Just bn <- pick (genBoundedNat (limit 10) 5)
184181
| Nothing => printLn "Failed"
185182
case bn of
186183
MkBounded n => putStrLn $ "Generated: " ++ show n ++ " (< 5)"
187184
```
188185

186+
Run it:
187+
188+
```bash
189+
echo -e ':exec testBounded' | rlwrap pack repl ./src/Playground.idr
190+
...
191+
Main> --- Generating BoundedNat with limit'=5 ---
192+
Generated: 4 (< 5)
193+
Generated: 1 (< 5)
194+
Generated: 1 (< 5)
195+
Generated: 3 (< 5)
196+
Generated: 4 (< 5)
197+
```
198+
189199
The `{auto prf : n `LT` limit}` constraint ensures that only values less than the limit are generated. `deriveGen` will automatically search for valid `n` values.
190200
191201
---
@@ -196,3 +206,11 @@ Now that you can generate proof-carrying data, you are ready for more advanced t
196206
197207
* **Want to integrate handwritten generators?** Continue to **[Mixing Manual and Automatic Generation](t09-mixing-manual-and-automatic.md)** to see how `deriveGen` automatically discovers and uses your custom generators.
198208
* **Want to understand the internals?** Continue to **[Under the Hood: Building a deriveGen-like Macro](t08-under-the-hood-a-derivegen-like-macro.md)** to learn how the derivation engine works.
209+
210+
<!-- idris
211+
main : IO ()
212+
main = do
213+
testList
214+
testFuel
215+
testBounded
216+
-->

0 commit comments

Comments
 (0)