my blog lives here now
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

332 lines
12 KiB

6 years ago
2 years ago
6 years ago
  1. ---
  2. title: Dependent types in Haskell - Sort of
  3. date: August 23, 2016
  4. synopsys: 2
  5. ---
  6. **Warning**: An intermediate level of type-fu is necessary for understanding
  7. *this post.
  8. The glorious Glasgow Haskell Compilation system, since around version 6.10 has
  9. had support for indexed type familes, which let us represent functional
  10. relationships between types. Since around version 7, it has also supported
  11. datatype-kind promotion, which lifts arbitrary data declarations to types. Since
  12. version 8, it has supported an extension called `TypeInType`, which unifies the
  13. kind and type level.
  14. With this in mind, we can implement the classical dependently-typed example:
  15. Length-indexed lists, also called `Vectors`{.haskell}.
  16. ----
  17. > {-# LANGUAGE TypeInType #-}
  18. `TypeInType` also implies `DataKinds`, which enables datatype promotion, and
  19. `PolyKinds`, which enables kind polymorphism.
  20. `TypeOperators` is needed for expressing type-level relationships infixly, and
  21. `TypeFamilies` actually lets us define these type-level functions.
  22. > {-# LANGUAGE TypeOperators #-}
  23. > {-# LANGUAGE TypeFamilies #-}
  24. Since these are not simple-kinded types, we'll need a way to set their kind
  25. signatures[^kind] explicitly. We'll also need Generalized Algebraic Data Types
  26. (or GADTs, for short) for defining these types.
  27. > {-# LANGUAGE KindSignatures #-}
  28. > {-# LANGUAGE GADTs #-}
  29. Since GADTs which couldn't normally be defined with regular ADT syntax can't
  30. have deriving clauses, we also need `StandaloneDeriving`.
  31. > {-# LANGUAGE StandaloneDeriving #-}
  32. > module Vector where
  33. > import Data.Kind
  34. ----
  35. Natural numbers
  36. ===============
  37. We could use the natural numbers (and singletons) implemented in `GHC.TypeLits`,
  38. but since those are not defined inductively, they're painful to use for our
  39. purposes.
  40. Recall the definition of natural numbers proposed by Giuseppe Peano in his
  41. axioms: **Z**ero is a natural number, and the **s**uccessor of a natural number
  42. is also a natural number.
  43. If you noticed the bold characters at the start of the words _zero_ and
  44. _successor_, you might have already assumed the definition of naturals to be
  45. given by the following GADT:
  46. < data Nat where
  47. < Z :: Nat
  48. < S :: Nat -> Nat
  49. This is fine if all you need are natural numbers at the _value_ level, but since
  50. we'll be parametrising the Vector type with these, they have to exist at the
  51. type level. The beauty of datatype promotion is that any promoted type will
  52. exist at both levels: A kind with constructors as its inhabitant types, and a
  53. type with constructors as its... constructors.
  54. Since we have TypeInType, this declaration was automatically lifted, but we'll
  55. use explicit kind signatures for clarity.
  56. > data Nat :: Type where
  57. > Z :: Nat
  58. > S :: Nat -> Nat
  59. The `Type` kind, imported from `Data.Kind`, is a synonym for the `*` (which will
  60. eventually replace the latter).
  61. Vectors
  62. =======
  63. Vectors, in dependently-typed languages, are lists that apart from their content
  64. encode their size along with their type.
  65. If we assume that lists can not have negative length, and an empty vector has
  66. length 0, this gives us a nice inductive definition using the natural number
  67. ~~type~~ kind[^kinds]
  68. > 1. An empty vector of `a` has size `Z`{.haskell}.
  69. > 2. Adding an element to the front of a vector of `a` and length `n` makes it
  70. > have length `S n`{.haskell}.
  71. We'll represent this in Haskell as a datatype with a kind signature of `Nat ->
  72. Type -> Type` - That is, it takes a natural number (remember, these were
  73. automatically lifted to kinds), a regular type, and produces a regular type.
  74. Note that, `->` still means a function at the kind level.
  75. > data Vector :: Nat -> Type -> Type where
  76. Or, without use of `Type`,
  77. < data Vector :: Nat -> * -> * where
  78. We'll call the empty vector `Nil`{.haskell}. Remember, it has size
  79. `Z`{.haskell}.
  80. > Nil :: Vector Z a
  81. Also note that type variables are implicit in the presence of kind signatures:
  82. They are assigned names in order of appearance.
  83. Consing onto a vector, represented by the infix constructor `:|`, sets its
  84. length to the successor of the existing length, and keeps the type of elements
  85. intact.
  86. > (:|) :: a -> Vector x a -> Vector (S x) a
  87. Since this constructor is infix, we also need a fixidity declaration. For
  88. consistency with `(:)`, cons for regular lists, we'll make it right-associative
  89. with a precedence of `5`.
  90. > infixr 5 :|
  91. We'll use derived `Show`{.haskell} and `Eq`{.haskell} instances for
  92. `Vector`{.haskell}, for clarity reasons. While the derived `Eq`{.haskell} is
  93. fine, one would prefer a nicer `Show`{.haskell} instance for a
  94. production-quality library.
  95. > deriving instance Show a => Show (Vector n a)
  96. > deriving instance Eq a => Eq (Vector n a)
  97. Slicing up Vectors {#slicing}
  98. ==================
  99. Now that we have a vector type, we'll start out by implementing the 4 basic
  100. operations for slicing up lists: `head`, `tail`, `init` and `last`.
  101. Since we're working with complicated types here, it's best to always use type
  102. signatures.
  103. Head and Tail {#head-and-tail}
  104. -------------
  105. Head is easy - It takes a vector with length `>1`, and returns its first
  106. element. This could be represented in two ways.
  107. < head :: (S Z >= x) ~ True => Vector x a -> a
  108. This type signature means that, if the type-expression `S Z >= x`{.haskell}
  109. unifies with the type `True` (remember - datakind promotion at work), then head
  110. takes a `Vector x a` and returns an `a`.
  111. There is, however, a much simpler way of doing the above.
  112. > head :: Vector (S x) a -> a
  113. That is, head takes a vector whose length is the successor of a natural number
  114. `x` and returns its first element.
  115. The implementation is just as concise as the one for lists:
  116. > head (x :| _) = x
  117. That's it. That'll type-check and compile.
  118. Trying, however, to use that function on an empty vector will result in a big
  119. scary type error:
  120. ```plain
  121. Vector> Vector.head Nil
  122. <interactive>:1:13: error:
  123. • Couldn't match type ‘'Z’ with ‘'S x0’
  124. Expected type: Vector ('S x0) a
  125. Actual type: Vector 'Z a
  126. • In the first argument of ‘Vector.head’, namely ‘Nil’
  127. In the expression: Vector.head Nil
  128. In an equation for ‘it’: it = Vector.head Nil
  129. ```
  130. Simplified, it means that while it was expecting the successor of a natural
  131. number, it got zero instead. This function is total, unlike the one in
  132. `Data.List`{.haskell}, which fails on the empty list.
  133. < head [] = error "Prelude.head: empty list"
  134. < head (x:_) = x
  135. Tail is just as easy, except in this case, instead of discarding the predecessor
  136. of the vector's length, we'll use it as the length of the resulting vector.
  137. This makes sense, as, logically, getting the tail of a vector removes its first
  138. length, thus "unwrapping" a level of `S`.
  139. > tail :: Vector (S x) a -> Vector x a
  140. > tail (_ :| xs) = xs
  141. Notice how neither of these have a base case for empty vectors. In fact, adding
  142. one will not typecheck (with the same type of error - Can't unify `Z`{.haskell}
  143. with `S x`{.haskell}, no matter how hard you try.)
  144. Init {#init}
  145. ----
  146. What does it mean to take the initial of an empty vector? That's obviously
  147. undefined, much like taking the tail of an empty vector. That is, `init` and
  148. `tail` have the same type signature.
  149. > init :: Vector (S x) a -> Vector x a
  150. The `init` of a singleton list is nil. This type-checks, as the list would have
  151. had length `S Z` (that is - 1), and now has length `Z`.
  152. > init (x :| Nil) = Nil
  153. To take the init of a vector with more than one element, all we do is recur on
  154. the tail of the list.
  155. > init (x :| y :| ys) = x :| Vector.init (y :| ys)
  156. That pattern is a bit weird - it's logically equivalent to `(x :|
  157. xs)`{.haskell}. But, for some reason, that doesn't make the typechecker happy,
  158. so we use the long form.
  159. Last {#last}
  160. ----
  161. Last can, much like the list version, be implemented in terms of a left fold.
  162. The type signature is like the one for head, and the fold is the same as that
  163. for lists. The foldable instance for vectors is given [here](#Foldable).
  164. > last :: Vector (S x) a -> a
  165. > last = foldl (\_ x -> x) impossible where
  166. Wait - what's `impossible`? Since this is a fold, we do still need an initial
  167. element - We could use a pointful fold with the head as the starting point, but
  168. I feel like this helps us to understand the power of dependently-typed vectors:
  169. That error will _never_ happen. Ever. That's why it's `impossible`!
  170. > impossible = error "Type checker, you have failed me!"
  171. That's it for the basic vector operations. We can now slice a vector anywhere
  172. that makes sense - Though, there's one thing missing: `uncons`.
  173. Uncons {#uncons}
  174. ------
  175. Uncons splits a list (here, a vector) into a pair of first element and rest.
  176. With lists, this is generally implemented as returning a `Maybe`{.haskell} type,
  177. but since we can encode the type of a vector in it's type, there's no need for
  178. that here.
  179. > uncons :: Vector (S x) a -> (a, Vector x a)
  180. > uncons (x :| xs) = (x, xs)
  181. Mapping over Vectors {#functor}
  182. ====================
  183. We'd like a `map` function that, much like the list equivalent, applies a
  184. function to all elements of a vector, and returns a vector with the same length.
  185. This operation should hopefully be homomorphic: That is, it keeps the structure
  186. of the list intact.
  187. The `base` package has a typeclass for this kind of morphism, can you guess what
  188. it is? If you guessed Functor, then you're right! If you didn't, you might
  189. aswell close the article now - Heavy type-fu inbound, though not right now.
  190. The functor instance is as simple as can be:
  191. > instance Functor (Vector x) where
  192. The fact that functor expects something of kind `* -> *`, we need to give the
  193. length in the instance head - And since we do that, the type checker guarantees
  194. that this is, in fact, a homomorphic relationship.
  195. Mapping over `Nil` just returns `Nil`.
  196. > f `fmap` Nil = Nil
  197. Mapping over a list is equivalent to applying the function to the first element,
  198. then recurring over the tail of the vector.
  199. > f `fmap` (x :| xs) = f x :| (fmap f xs)
  200. We didn't really need an instance of Functor, but I think standalone map is
  201. silly.
  202. Folding Vectors {#foldable}
  203. ===============
  204. The Foldable class head has the same kind signature as the Functor class head:
  205. `(* -> *) -> Constraint` (where `Constraint` is the kind of type classes), that
  206. is, it's defined by the class head
  207. < class Foldable (t :: Type -> Type) where
  208. So, again, the length is given in the instance head.
  209. > instance Foldable (Vector x) where
  210. > foldr f z Nil = z
  211. > foldr f z (x :| xs) = f x $ foldr f z xs
  212. This is _exactly_ the Foldable instance for `[a]`, except the constructors are
  213. different. Hopefully, by now you've noticed that Vectors have the same
  214. expressive power as lists, but with more safety enforced by the type checker.
  215. Conclusion
  216. ==========
  217. Two thousand words in, we have an implementation of functorial, foldable vectors
  218. with implementations of `head`, `tail`, `init`, `last` and `uncons`. Since
  219. going further (implementing `++`, since a Monoid instance is impossible) would
  220. require implementing closed type familes, we'll leave that for next time.
  221. Next time, we'll tackle the implementation of `drop`, `take`, `index` (`!!`, but
  222. for vectors), `append`, `length`, and many other useful list functions.
  223. Eventually, you'd want an implementation of all functions in `Data.List`. We
  224. shall tackle `filter` in a later issue.
  225. [^kind]: You can read about [Kind polymorphism and
  226. Type-in-Type](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#kind-polymorphism-and-type-in-type)
  227. in the GHC manual.
  228. [^kinds]: The TypeInType extension unifies the type and kind level, but this
  229. article still uses the word `kind` throughout. This is because it's easier to
  230. reason about types, datatype promotion and type familes if you have separate
  231. type and kind levels.