Skip to content

Commit 6390256

Browse files
committed
Factor Chapter 6 Into Subchapters
1 parent 9d8273b commit 6390256

File tree

10 files changed

+611
-574
lines changed

10 files changed

+611
-574
lines changed

src/SUMMARY.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,22 @@
2222
- [Conclusion](./Tutorial/Functions1/Conclusion.md)
2323
- [Algebraic Data Types](./Tutorial/DataTypes.md)
2424
- [Enumerations](./Tutorial/DataTypes/Enumerations.md)
25-
- [Exercises part 1](./Tutorial/DataTypes/Exercises1.md)
25+
- [Exercises](./Tutorial/DataTypes/Exercises1.md)
2626
- [Sum Types](./Tutorial/DataTypes/SumTypes.md)
27-
- [Exercises part 2](./Tutorial/DataTypes/Exercises2.md)
27+
- [Exercises](./Tutorial/DataTypes/Exercises2.md)
2828
- [Records](./Tutorial/DataTypes/Records.md)
29-
- [Exercises part 3](./Tutorial/DataTypes/Exercises3.md)
29+
- [Exercises](./Tutorial/DataTypes/Exercises3.md)
3030
- [Generic Data Types](./Tutorial/DataTypes/GenericDataTypes.md)
31-
- [Exercises part 4](./Tutorial/DataTypes/Exercises4.md)
31+
- [Exercises](./Tutorial/DataTypes/Exercises4.md)
3232
- [Alternative Syntax for Data Definitions](./Tutorial/DataTypes/AltSyntax.md)
3333
- [Conclusion](./Tutorial/DataTypes/Conclusion.md)
3434
- [Interfaces](./Tutorial/Interfaces.md)
3535
- [Interface Basics](./Tutorial/Interfaces/Basics.md)
36-
- [Exercises Part 1](./Tutorial/Interfaces/Exercises1.md)
36+
- [Exercises](./Tutorial/Interfaces/Exercises1.md)
3737
- [More About Interfaces](./Tutorial/Interfaces/More.md)
38-
- [Exercises Part 2](./Tutorial/Interfaces/Exercises2.md)
38+
- [Exercises](./Tutorial/Interfaces/Exercises2.md)
3939
- [Interfaces in the Prelude](./Tutorial/Interfaces/Prelude.md)
40-
- [Exercises Part 3](./Tutorial/Interfaces/Exercises3.md)
40+
- [Exercises](./Tutorial/Interfaces/Exercises3.md)
4141
- [Conclusion](./Tutorial/Interfaces/Conclusion.md)
4242
- [Functions Part 2](./Tutorial/Functions2.md)
4343
- [Let Bindings and Local Definitions](./Tutorial/Functions2/LetBindings.md)
@@ -46,6 +46,13 @@
4646
- [Programming with Holes](./Tutorial/Functions2/Holes.md)
4747
- [Conclusion](./Tutorial/Functions2/Conclusion.md)
4848
- [Dependent Types](./Tutorial/Dependent.md)
49+
- [Length-Indexed Lists](./Tutorial/Dependent/LengthIndexedLists.md)
50+
- [Exercises](./Tutorial/Dependent/Exercises1.md)
51+
- [Fin: Safe Indexing Into Vectors](./Tutorial/Dependent/Fin.md)
52+
- [Exercises](./Tutorial/Dependent/Exercises2.md)
53+
- [Compile-Time Computations](./Tutorial/Dependent/Comptime.md)
54+
- [Exercises](./Tutorial/Dependent/Exercises3.md)
55+
- [Conclusion](./Tutorial/Dependent/Conclusion.md)
4956
- [IO: Programming with Side Effects](./Tutorial/IO.md)
5057
- [Functor and Friends](./Tutorial/Functor.md)
5158
- [Recursion and Folds](./Tutorial/Folds.md)

src/Tutorial/Dependent.md

Lines changed: 0 additions & 567 deletions
Large diffs are not rendered by default.

src/Tutorial/Dependent/Comptime.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Compile-Time Computations
2+
3+
```idris
4+
module Tutorial.Dependent.Comptime
5+
6+
import Tutorial.Dependent.LengthIndexedLists
7+
8+
%default total
9+
```
10+
11+
In the last section - especially in some of the exercises - we started more and more to use compile time computations to describe the types of our functions and values. This is a very powerful concept, as it allows us to compute output types from input types. Here's an example:
12+
13+
It is possible to concatenate two `List`s with the `(++)` operator. Surely, this should also be possible for `Vect`. But `Vect` is indexed by its length, so we have to reflect in the types exactly how the lengths of the inputs affect the lengths of the output. Here's how to do this:
14+
15+
```idris
16+
(++) : Vect m a -> Vect n a -> Vect (m + n) a
17+
(++) [] ys = ys
18+
(++) (x :: xs) ys = x :: (xs ++ ys)
19+
```
20+
21+
Note, how we keep track of the lengths at the type-level, again ruling out certain common programming errors like inadvertently dropping some values.
22+
23+
We can also use type-level computations as patterns on the input types. Here is an alternative type and implementation for `drop`, which you implemented in the exercises by using a `Fin n` argument:
24+
25+
```idris
26+
drop' : (m : Nat) -> Vect (m + n) a -> Vect n a
27+
drop' 0 xs = xs
28+
drop' (S k) (_ :: xs) = drop' k xs
29+
```
30+
31+
Note that changing the order from `(m + n)` to `(n + m)` in the second parameter will cause an error at the second `xs`:
32+
33+
```repl
34+
While processing right hand side of drop'. Can't solve constraint between: plus n 0 and n.
35+
```
36+
37+
You will learn why in the next section.
38+
39+
## Limitations
40+
41+
After all the examples and exercises in this section you might have come to the conclusion that we can use arbitrary expressions in the types and Idris will happily evaluate and unify all of them for us.
42+
43+
I'm afraid that's not even close to the truth. The examples in this section were hand-picked because they are known to *just work*. The reason being, that there was always a direct link between our own pattern matches and the implementations of functions we used at compile time.
44+
45+
For instance, here is the implementation of addition of natural numbers:
46+
47+
```idris
48+
add : Nat -> Nat -> Nat
49+
add Z n = n
50+
add (S k) n = S $ add k n
51+
```
52+
53+
As you can see, `add` is implemented via a pattern match on its *first* argument, while the second argument is never inspected. Note, how this is exactly how `(++)` for `Vect` is implemented: There, we also pattern match on the first argument, returning the second unmodified in the `Nil` case, and prepending the head to the result of appending the tail in the *cons* case. Since there is a direct correspondence between the two pattern matches, it is possible for Idris to unify `0 + n` with `n` in the `Nil` case, and `(S k) + n` with `S (k + n)` in the *cons* case.
54+
55+
Here is a simple example, where Idris will not longer be convinced without some help from us:
56+
57+
```idris
58+
failing "Can't solve constraint"
59+
reverse : Vect n a -> Vect n a
60+
reverse [] = []
61+
reverse (x :: xs) = reverse xs ++ [x]
62+
```
63+
64+
When we type-check the above, Idris will fail with the following error message: "Can't solve constraint between: plus n 1 and S n." Here's what's going on: From the pattern match on the left hand side, Idris knows that the length of the vector is `S n`, for some natural number `n` corresponding to the length of `xs`. The length of the vector on the right hand side is `n + 1`, according to the type of `(++)` and the lengths of `xs` and `[x]`. Overloaded operator `(+)` is implemented via function `Prelude.plus`, that's why Idris replaces `(+)` with `plus` in the error message.
65+
66+
As you can see from the above, Idris can't verify on its own that `1 + n` is the same thing as `n + 1`. It can accept some help from us, though. If we come up with a *proof* that the above equality holds (or - more generally - that our implementation of addition for natural numbers is *commutative*), we can use this proof to *rewrite* the types on the right hand side of `reverse`. Writing proofs and using `rewrite` will require some in-depth explanations and examples. Therefore, these things will have to wait until another chapter.
67+
68+
## Unrestricted Implicits
69+
70+
In functions like `replicate`, we pass a natural number `n` as an explicit, unrestricted argument from which we infer the length of the vector to return. In some circumstances, `n` can be inferred from the context. For instance, in the following example it is tedious to pass `n` explicitly:
71+
72+
```idris
73+
ex4 : Vect 3 Integer
74+
ex4 = zipWith (*) (replicate 3 10) (replicate 3 11)
75+
```
76+
77+
The value `n` is clearly derivable from the context, which can be confirmed by replacing it with underscores:
78+
79+
```idris
80+
ex5 : Vect 3 Integer
81+
ex5 = zipWith (*) (replicate _ 10) (replicate _ 11)
82+
```
83+
84+
We therefore can implement an alternative version of `replicate`, where we pass `n` as an implicit argument of *unrestricted* quantity:
85+
86+
```idris
87+
replicate' : {n : _} -> a -> Vect n a
88+
replicate' = replicate n
89+
```
90+
91+
Note how, in the implementation of `replicate'`, we can refer to `n` and pass it as an explicit argument to `replicate`.
92+
93+
Deciding whether to pass potentially inferable arguments to a function implicitly or explicitly is a question of how often the arguments actually *are* inferable by Idris. Sometimes it might even be useful to have both versions of a function. Remember, however, that even in case of an implicit argument we can still pass the value explicitly:
94+
95+
```idris
96+
ex6 : Vect ? Bool
97+
ex6 = replicate' {n = 2} True
98+
```
99+
100+
In the type signature above, the question mark (`?`) means, that Idris should try and figure out the value on its own by unification. This forces us to specify `n` explicitly on the right hand side of `ex6`.
101+
102+
### Pattern Matching on Implicits
103+
104+
The implementation of `replicate'` makes use of function `replicate`, where we could pattern match on the explicit argument `n`. However, it is also possible to pattern match on implicit, named arguments of non-zero quantity:
105+
106+
```idris
107+
replicate'' : {n : _} -> a -> Vect n a
108+
replicate'' {n = Z} _ = Nil
109+
replicate'' {n = S _} v = v :: replicate'' v
110+
```
111+
112+
<!-- vi: filetype=idris2:syntax=markdown
113+
-->
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Conclusion
2+
3+
- Dependent types allow us to calculate types from values. This makes it possible to encode properties of values at the type-level and verify these properties at compile time.
4+
5+
- Length-indexed lists (vectors) let us rule out certain implementation errors, by forcing us to be precise about the lengths of input and output vectors.
6+
7+
- We can use patterns in type signatures, for instance to express that the length of a vector is non-zero and therefore, the vector is non-empty.
8+
9+
- When creating values of a type family, the values of the indices need to be known at compile time, or they need to be passed as arguments to the function creating the values, where we can pattern match on them to figure out, which constructors to use.
10+
11+
- We can use `Fin n`, the type of natural numbers strictly smaller than `n`, to safely index into a vector of length `n`.
12+
13+
- Sometimes, it is convenient to pass inferable arguments as non-erased implicits, in which case we can still inspect them by pattern matching or pass them to other functions, while Idris will try and fill in the values for us.
14+
15+
Note, that data type `Vect` together with many of the functions we implemented here is available from module `Data.Vect` from the *base* library. Likewise, `Fin` is available from `Data.Fin` from *base*.
16+
17+
## What's next
18+
19+
In the [next section](IO.md), it is time to learn how to write effectful programs and how to do this while still staying *pure*.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
## Exercises part 1
2+
3+
01. Implement a function `len : List a -> Nat` for calculating the length of a `List`. For example, `len [1, 1, 1]` produces `3`.
4+
5+
02. Implement function `head` for non-empty vectors:
6+
7+
```idris
8+
head : Vect (S n) a -> a
9+
```
10+
11+
Note, how we can describe non-emptiness by using a *pattern* in the length of `Vect`. This rules out the `Nil` case, and we can return a value of type `a`, without having to wrap it in a `Maybe`! Make sure to add an `impossible` clause for the `Nil` case (although this is not strictly necessary here).
12+
13+
03. Using `head` as a reference, declare and implement function `tail` for non-empty vectors. The types should reflect that the output is exactly one element shorter than the input.
14+
15+
04. Implement `zipWith3`. If possible, try to doing so without looking at the implementation of `zipWith`:
16+
17+
```idris
18+
zipWith3 : (a -> b -> c -> d) -> Vect n a -> Vect n b -> Vect n c -> Vect n d
19+
```
20+
21+
05. Declare and implement a function `foldSemi` for accumulating the values stored in a `List` through `Semigroup`s append operator (`(<+>)`). (Make sure to only use a `Semigroup` constraint, as opposed to a `Monoid` constraint.)
22+
23+
06. Do the same as in Exercise 4, but for non-empty vectors. How does a vector's non-emptiness affect the output type?
24+
25+
07. Given an initial value of type `a` and a function `a -> a`, we'd like to generate `Vect`s of `a`s, the first value of which is `a`, the second value being `f a`, the third being `f (f a)` and so on.
26+
27+
For instance, if `a` is 1 and `f` is `(* 2)`, we'd like to get results similar to the following: `[1,2,4,8,16,...]`.
28+
29+
Declare and implement function `iterate`, which should encapsulate this behavior. Get some inspiration from `replicate` if you don't know where to start.
30+
31+
08. Given an initial value of a state type `s` and a function `fun : s -> (s,a)`, we'd like to generate `Vect`s of `a`s. Declare and implement function `generate`, which should encapsulate this behavior. Make sure to use the updated state in every new invocation of `fun`.
32+
33+
Here's an example how this can be used to generate the first `n` Fibonacci numbers:
34+
35+
```repl
36+
generate 10 (\(x,y) => let z = x + y in ((y,z),z)) (0,1)
37+
[1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
38+
```
39+
40+
09. Implement function `fromList`, which converts a list of values to a `Vect` of the same length. Use holes if you get stuck:
41+
42+
```idris
43+
fromList : (as : List a) -> Vect (length as) a
44+
```
45+
46+
Note how, in the type of `fromList`, we can *calculate* the length of the resulting vector by passing the list argument to function *length*.
47+
48+
10. Consider the following declarations:
49+
50+
```idris
51+
maybeSize : Maybe a -> Nat
52+
53+
fromMaybe : (m : Maybe a) -> Vect (maybeSize m) a
54+
```
55+
56+
Choose a reasonable implementation for `maybeSize` and implement `fromMaybe` afterwards.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Exercises part 2
2+
3+
1. Implement function `update`, which, given a function of type `a -> a`, updates the value in a`Vect n a` at position `k < n`.
4+
5+
2. Implement function `insert`, which inserts a value of type `a` at position `k <= n` in a `Vect n a`. Note, that `k` is the index of the freshly inserted value, so that the following holds:
6+
7+
```repl
8+
index k (insert k v vs) = v
9+
```
10+
11+
3. Implement function `delete`, which deletes a value from a vector at the given index.
12+
13+
This is trickier than Exercises 1 and 2, as we have to properly encode in the types that the vector is getting one element shorter.
14+
15+
4. We can use `Fin` to implement safe indexing into `List`s as well. Try to come up with a type and implementation for `safeIndexList`.
16+
17+
Note: If you don't know how to start, look at the type of `fromList` for some inspiration. You might also need give the arguments in a different order than for `index`.
18+
19+
5. Implement function `finToNat`, which converts a `Fin n` to the corresponding natural number, and use this to declare and implement function `take` for splitting of the first `k` elements of a `Vect n a` with `k <= n`.
20+
21+
6. Implement function `minus` for subtracting a value `k` from a natural number `n` with `k <= n`.
22+
23+
7. Use `minus` from Exercise 6 to declare and implement function `drop`, for dropping the first `k` values from a `Vect n a`, with `k <= n`.
24+
25+
8. Implement function `splitAt` for splitting a `Vect n a` at position `k <= n`, returning the prefix and suffix of the vector wrapped in a pair.
26+
27+
Hint: Use `take` and `drop` in your implementation.
28+
29+
Hint: Since `Fin n` consists of the values strictly smaller than `n`, `Fin (S n)` consists of the values smaller than or equal to `n`.
30+
31+
Note: Functions `take`, `drop`, and `splitAt`, while correct and provably total, are rather cumbersome to type. There is an alternative way to declare their types, as we will see in the next section.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Exercises part 3
2+
3+
1. Here is a function declaration for flattening a `List` of `List`s:
4+
5+
```idris
6+
flattenList : List (List a) -> List a
7+
```
8+
9+
Implement `flattenList` and declare and implement a similar function `flattenVect` for flattening vectors of vectors.
10+
11+
2. Implement functions `take'` and `splitAt'` like in the exercises of the previous section but using the technique shown for `drop'`.
12+
13+
3. Implement function `transpose` for converting an `m x n`-matrix (represented as a `Vect m (Vect n a)`) to an `n x m`-matrix.
14+
15+
Note: This might be a challenging exercise, but make sure to give it a try. As usual, make use of holes if you get stuck!
16+
17+
Here is an example how this should work in action:
18+
19+
```repl
20+
Solutions.Dependent> transpose [[1,2,3],[4,5,6]]
21+
[[1, 4], [2, 5], [3, 6]]
22+
```

src/Tutorial/Dependent/Fin.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# `Fin`: Safe Indexing into Vectors
2+
3+
```idris
4+
module Tutorial.Dependent.Fin
5+
6+
import Tutorial.Dependent.LengthIndexedLists
7+
8+
%default total
9+
```
10+
11+
Consider function `index`, which tries to extract a value from a `List` at the given position:
12+
13+
```idris
14+
indexList : (pos : Nat) -> List a -> Maybe a
15+
indexList _ [] = Nothing
16+
indexList 0 (x :: _) = Just x
17+
indexList (S k) (_ :: xs) = indexList k xs
18+
```
19+
20+
Now, here is a thing to consider when writing functions like `indexList`: Do we want to express the possibility of failure in the output type, or do we want to restrict the accepted arguments, so the function can no longer fail? These are important design decisions, especially in larger applications. Returning a `Maybe` or `Either` from a function forces client code to eventually deal with the `Nothing` or `Left` case, and until this happens, all intermediary results will carry the `Maybe` or `Either` stain, which will make it more cumbersome to run calculations with these intermediary results. On the other hand, restricting the values accepted as input will complicate the argument types and will put the burden of input validation on our functions' callers, (although, at compile time we can get help from Idris, as we will see when we talk about auto implicits) while keeping the output pure and clean.
21+
22+
Languages without dependent types (like Haskell), can often only take the route described above: To wrap the result in a `Maybe` or `Either`. However, in Idris we can often *refine* the input types to restrict the set of accepted values, thus ruling out the possibility of failure.
23+
24+
Assume, as an example, we'd like to extract a value from a `Vect n a` at (zero-based) index `k`. Surely, this can succeed if and only if `k` is a natural number strictly smaller than the length `n` of the vector. Luckily, we can express this precondition in an indexed type:
25+
26+
```idris
27+
data Fin : (n : Nat) -> Type where
28+
FZ : {0 n : Nat} -> Fin (S n)
29+
FS : (k : Fin n) -> Fin (S n)
30+
```
31+
32+
`Fin n` is the type of natural numbers strictly smaller than `n`. It is defined inductively: `FZ` corresponds to natural number *zero*, which, as can be seen in its type, is strictly smaller than `S n` for any natural number `n`. `FS` is the inductive case: If `k` is strictly smaller than `n` (`k` being of type `Fin n`), then `FS k` is strictly smaller than `S n`.
33+
34+
Let's come up with some values of type `Fin`:
35+
36+
```idris
37+
fin0_5 : Fin 5
38+
fin0_5 = FZ
39+
40+
fin0_7 : Fin 7
41+
fin0_7 = FZ
42+
43+
fin1_3 : Fin 3
44+
fin1_3 = FS FZ
45+
46+
fin4_5 : Fin 5
47+
fin4_5 = FS (FS (FS (FS FZ)))
48+
```
49+
50+
Note, that there is no value of type `Fin 0`. We will learn in a later session, how to express "there is no value of type `x`" in a type.
51+
52+
Let us now check, whether we can use `Fin` to safely index into a `Vect`:
53+
54+
```idris
55+
index : Fin n -> Vect n a -> a
56+
```
57+
58+
Before you continue, try to implement `index` yourself, making use of holes if you get stuck.
59+
60+
```idris
61+
index FZ (x :: _) = x
62+
index (FS k) (_ :: xs) = index k xs
63+
```
64+
65+
Note, how there is no `Nil` case and the totality checker is still happy. That's because `Nil` is of type `Vect 0 a`, but there is no value of type `Fin 0`! We can verify this by adding the missing impossible clauses:
66+
67+
```idris
68+
index FZ Nil impossible
69+
index (FS _) Nil impossible
70+
```
71+
72+
<!-- vi: filetype=idris2:syntax=markdown
73+
-->

0 commit comments

Comments
 (0)