├── .gitignore ├── 01-GADTs ├── exercise01.cabal └── src │ ├── Exercises.hs │ └── GADTs.hs ├── 02-FlexibleInstances ├── exercise02.cabal └── src │ ├── Exercises.hs │ └── FlexibleInstances.hs ├── 03-KindSignatures ├── exercise03.cabal └── src │ ├── Exercises.hs │ └── KindSignatures.hs ├── 04-DataKinds ├── exercise04.cabal └── src │ ├── DataKinds.hs │ └── Exercises.hs ├── 05-RankNTypes ├── exercise05.cabal └── src │ ├── Exercises.hs │ └── RankNTypes.hs ├── 06-TypeFamilies ├── exercise06.cabal └── src │ ├── Exercises.hs │ └── TypeFamilies.hs ├── 07-ConstraintKinds ├── exercise07.cabal └── src │ ├── ConstraintKinds.hs │ └── Exercises.hs ├── 08-PolyKinds ├── exercise08.cabal └── src │ ├── Exercises.hs │ └── PolyKinds.hs ├── 09-MultiParamTypeClasses ├── exercise09.cabal └── src │ ├── Exercises.hs │ └── MultiParamTypeClasses.hs ├── 10-FunctionalDependencies ├── exercise10.cabal └── src │ ├── Exercises.hs │ └── FunctionalDependencies.hs ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | */dist/* 2 | */dist-newstyle/* 3 | *.ghc.environment* 4 | -------------------------------------------------------------------------------- /01-GADTs/exercise01.cabal: -------------------------------------------------------------------------------- 1 | name: exercise01 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: GADTs 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /01-GADTs/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | module Exercises where 3 | 4 | 5 | 6 | 7 | 8 | {- ONE -} 9 | 10 | -- | Let's introduce a new class, 'Countable', and some instances to match. 11 | class Countable a where count :: a -> Int 12 | instance Countable Int where count = id 13 | instance Countable [a] where count = length 14 | instance Countable Bool where count x = if x then 1 else 0 15 | 16 | -- | a. Build a GADT, 'CountableList', that can hold a list of 'Countable' 17 | -- things. 18 | 19 | data CountableList where 20 | -- ... 21 | 22 | 23 | -- | b. Write a function that takes the sum of all members of a 'CountableList' 24 | -- once they have been 'count'ed. 25 | 26 | countList :: CountableList -> Int 27 | countList = error "Implement me!" 28 | 29 | 30 | -- | c. Write a function that removes all elements whose count is 0. 31 | 32 | dropZero :: CountableList -> CountableList 33 | dropZero = error "Implement me!" 34 | 35 | 36 | -- | d. Can we write a function that removes all the things in the list of type 37 | -- 'Int'? If not, why not? 38 | 39 | filterInts :: CountableList -> CountableList 40 | filterInts = error "Contemplate me!" 41 | 42 | 43 | 44 | 45 | 46 | {- TWO -} 47 | 48 | -- | a. Write a list that can take /any/ type, without any constraints. 49 | 50 | data AnyList where 51 | -- ... 52 | 53 | -- | b. How many of the following functions can we implement for an 'AnyList'? 54 | 55 | reverseAnyList :: AnyList -> AnyList 56 | reverseAnyList = undefined 57 | 58 | filterAnyList :: (a -> Bool) -> AnyList -> AnyList 59 | filterAnyList = undefined 60 | 61 | lengthAnyList :: AnyList -> Int 62 | lengthAnyList = undefined 63 | 64 | foldAnyList :: Monoid m => AnyList -> m 65 | foldAnyList = undefined 66 | 67 | isEmptyAnyList :: AnyList -> Bool 68 | isEmptyAnyList = undefined 69 | 70 | instance Show AnyList where 71 | show = error "What about me?" 72 | 73 | 74 | 75 | 76 | 77 | {- THREE -} 78 | 79 | -- | Consider the following GADT: 80 | 81 | data TransformableTo output where 82 | TransformWith 83 | :: (input -> output) 84 | -> input 85 | -> TransformableTo output 86 | 87 | -- | ... and the following values of this GADT: 88 | 89 | transformable1 :: TransformableTo String 90 | transformable1 = TransformWith show 2.5 91 | 92 | transformable2 :: TransformableTo String 93 | transformable2 = TransformWith (uncurry (++)) ("Hello,", " world!") 94 | 95 | -- | a. Which type variable is existential inside 'TransformableTo'? What is 96 | -- the only thing we can do to it? 97 | 98 | -- | b. Could we write an 'Eq' instance for 'TransformableTo'? What would we be 99 | -- able to check? 100 | 101 | -- | c. Could we write a 'Functor' instance for 'TransformableTo'? If so, write 102 | -- it. If not, why not? 103 | 104 | 105 | 106 | 107 | 108 | {- FOUR -} 109 | 110 | -- | Here's another GADT: 111 | 112 | data EqPair where 113 | EqPair :: Eq a => a -> a -> EqPair 114 | 115 | -- | a. There's one (maybe two) useful function to write for 'EqPair'; what is 116 | -- it? 117 | 118 | -- | b. How could we change the type so that @a@ is not existential? (Don't 119 | -- overthink it!) 120 | 121 | -- | c. If we made the change that was suggested in (b), would we still need a 122 | -- GADT? Or could we now represent our type as an ADT? 123 | 124 | 125 | 126 | 127 | 128 | {- FIVE -} 129 | 130 | -- | Perhaps a slightly less intuitive feature of GADTs is that we can set our 131 | -- type parameters (in this case @a@) to different types depending on the 132 | -- constructor. 133 | 134 | data MysteryBox a where 135 | EmptyBox :: MysteryBox () 136 | IntBox :: Int -> MysteryBox () -> MysteryBox Int 137 | StringBox :: String -> MysteryBox Int -> MysteryBox String 138 | BoolBox :: Bool -> MysteryBox String -> MysteryBox Bool 139 | 140 | -- | When we pattern-match, the type-checker is clever enough to 141 | -- restrict the branches we have to check to the ones that could produce 142 | -- something of the given type. 143 | 144 | getInt :: MysteryBox Int -> Int 145 | getInt (IntBox int _) = int 146 | 147 | -- | a. Implement the following function by returning a value directly from a 148 | -- pattern-match: 149 | 150 | getInt' :: MysteryBox String -> Int 151 | getInt' _doSomeCleverPatternMatching = error "Return that value" 152 | 153 | -- | b. Write the following function. Again, don't overthink it! 154 | 155 | countLayers :: MysteryBox a -> Int 156 | countLayers = error "Implement me" 157 | 158 | -- | c. Try to implement a function that removes one layer of "Box". For 159 | -- example, this should turn a BoolBox into a StringBox, and so on. What gets 160 | -- in our way? What would its type be? 161 | 162 | 163 | 164 | 165 | 166 | {- SIX -} 167 | 168 | -- | We can even use our type parameters to keep track of the types inside an 169 | -- 'HList'! For example, this heterogeneous list contains no existentials: 170 | 171 | data HList a where 172 | HNil :: HList () 173 | HCons :: head -> HList tail -> HList (head, tail) 174 | 175 | exampleHList :: HList (String, (Int, (Bool, ()))) 176 | exampleHList = HCons "Tom" (HCons 25 (HCons True HNil)) 177 | 178 | -- | a. Write a 'head' function for this 'HList' type. This head function 179 | -- should be /safe/: you can use the type signature to tell GHC that you won't 180 | -- need to pattern-match on HNil, and therefore the return type shouldn't be 181 | -- wrapped in a 'Maybe'! 182 | 183 | -- | b. Currently, the tuples are nested. Can you pattern-match on something of 184 | -- type @HList (Int, String, Bool, ())@? Which constructor would work? 185 | 186 | patternMatchMe :: HList (Int, String, Bool, ()) -> Int 187 | patternMatchMe = undefined 188 | 189 | -- | c. Can you write a function that appends one 'HList' to the end of 190 | -- another? What problems do you run into? 191 | 192 | 193 | 194 | 195 | 196 | {- SEVEN -} 197 | 198 | -- | Here are two data types that may help: 199 | 200 | data Empty 201 | data Branch left centre right 202 | 203 | -- | a. Using these, and the outline for 'HList' above, build a heterogeneous 204 | -- /tree/. None of the variables should be existential. 205 | 206 | data HTree a where 207 | -- ... 208 | 209 | -- | b. Implement a function that deletes the left subtree. The type should be 210 | -- strong enough that GHC will do most of the work for you. Once you have it, 211 | -- try breaking the implementation - does it type-check? If not, why not? 212 | 213 | -- | c. Implement 'Eq' for 'HTree's. Note that you might have to write more 214 | -- than one to cover all possible HTrees. You might also need an extension or 215 | -- two, so look out for something... flexible... in the error messages! 216 | -- Recursion is your friend here - you shouldn't need to add a constraint to 217 | -- the GADT! 218 | 219 | 220 | 221 | 222 | 223 | {- EIGHT -} 224 | 225 | -- | a. Implement the following GADT such that values of this type are lists of 226 | -- values alternating between the two types. For example: 227 | -- 228 | -- @ 229 | -- f :: AlternatingList Bool Int 230 | -- f = ACons True (ACons 1 (ACons False (ACons 2 ANil))) 231 | -- @ 232 | 233 | data AlternatingList a b where 234 | -- ... 235 | 236 | -- | b. Implement the following functions. 237 | 238 | getFirsts :: AlternatingList a b -> [a] 239 | getFirsts = error "Implement me!" 240 | 241 | getSeconds :: AlternatingList a b -> [b] 242 | getSeconds = error "Implement me, too!" 243 | 244 | -- | c. One more for luck: write this one using the above two functions, and 245 | -- then write it such that it only does a single pass over the list. 246 | 247 | foldValues :: (Monoid a, Monoid b) => AlternatingList a b -> (a, b) 248 | foldValues = error "Implement me, three!" 249 | 250 | 251 | 252 | 253 | 254 | {- NINE -} 255 | 256 | -- | Here's the "classic" example of a GADT, in which we build a simple 257 | -- expression language. Note that we use the type parameter to make sure that 258 | -- our expression is well-formed. 259 | 260 | data Expr a where 261 | Equals :: Expr Int -> Expr Int -> Expr Bool 262 | Add :: Expr Int -> Expr Int -> Expr Int 263 | If :: Expr Bool -> Expr a -> Expr a -> Expr a 264 | IntValue :: Int -> Expr Int 265 | BoolValue :: Bool -> Expr Bool 266 | 267 | -- | a. Implement the following function and marvel at the typechecker: 268 | 269 | eval :: Expr a -> a 270 | eval = error "Implement me" 271 | 272 | -- | b. Here's an "untyped" expression language. Implement a parser from this 273 | -- into our well-typed language. Note that (until we cover higher-rank 274 | -- polymorphism) we have to fix the return type. Why do you think this is? 275 | 276 | data DirtyExpr 277 | = DirtyEquals DirtyExpr DirtyExpr 278 | | DirtyAdd DirtyExpr DirtyExpr 279 | | DirtyIf DirtyExpr DirtyExpr DirtyExpr 280 | | DirtyIntValue Int 281 | | DirtyBoolValue Bool 282 | 283 | parse :: DirtyExpr -> Maybe (Expr Int) 284 | parse = error "Implement me" 285 | 286 | -- | c. Can we add functions to our 'Expr' language? If not, why not? What 287 | -- other constructs would we need to add? Could we still avoid 'Maybe' in the 288 | -- 'eval' function? 289 | 290 | 291 | 292 | 293 | 294 | {- TEN -} 295 | 296 | -- | Back in the glory days when I wrote JavaScript, I could make a composition 297 | -- list like @pipe([f, g, h, i, j])@, and it would pass a value from the left 298 | -- side of the list to the right. In Haskell, I can't do that, because the 299 | -- functions all have to have the same type :( 300 | 301 | -- | a. Fix that for me - write a list that allows me to hold any functions as 302 | -- long as the input of one lines up with the output of the next. 303 | 304 | data TypeAlignedList a b where 305 | -- ... 306 | 307 | -- | b. Which types are existential? 308 | 309 | -- | c. Write a function to append type-aligned lists. This is almost certainly 310 | -- not as difficult as you'd initially think. 311 | 312 | composeTALs :: TypeAlignedList b c -> TypeAlignedList a b -> TypeAlignedList a c 313 | composeTALs = error "Implement me, and then celebrate!" 314 | 315 | -------------------------------------------------------------------------------- /01-GADTs/src/GADTs.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | 3 | module {- 1, in which we'll cover -} GADTs {- in isolation. This is very basic, 4 | and we'll see -} where {- they really shine when we come to things like 5 | ConstraintKinds and DataKinds. -} 6 | 7 | --- 8 | 9 | {- 10 | In standard Haskell, we have the notion of a homogeneous list. That is to 11 | say, a list in which all the values are of the same type: 12 | -} 13 | 14 | data List a = Nil | Cons a (List a) 15 | 16 | {- 17 | This is fine, and useful enough. We can, for example, write a 'Show' 18 | instance for this: 19 | -} 20 | 21 | instance Show a => Show (List a) where 22 | show Nil = "[]" 23 | show (Cons head tail) = show head ++ " : " ++ show tail 24 | 25 | {- 26 | With this, we can show a list of values as long as the type of its values 27 | can itself be shown. The example below, for example, yields "2 : 3 : []". 28 | -} 29 | 30 | example1 :: String 31 | example1 = show (Cons 2 (Cons 3 Nil)) 32 | 33 | {- 34 | What we get from a standard algebraic data type declaration, such as 35 | 'List', is a /function/ for each of its constructors. By writing the 'List' 36 | declaration above, we have brought the following functions into scope: 37 | 38 | Nil :: List a 39 | Cons :: a -> List a -> List a 40 | 41 | OK, 'Nil' isn't really a function, but you get the picture: we take the 42 | constructors' arguments as inputs, and produce a value of the type as the 43 | output. However, we should notice some restrictions: 44 | 45 | - We can't mention type variables in the arguments unless they're also in 46 | the result. 47 | 48 | - We can't /constrain/ the values of the input using any typeclasses. 49 | 50 | Usually, this is fine, but sometimes it's inconvenient. For example, if we 51 | know we're going to be 'show'ing everything in a list, why do we need all 52 | members of that list to be of the same type? Surely, all we actually need is 53 | a guarantee that the members all have a 'Show' instance: 54 | 55 | ShowNil :: ShowList 56 | ShowCons :: Show a => a -> ShowList -> ShowList 57 | 58 | Notice here that we've broken both of the above rules: 59 | 60 | - We mention a variable, @a@, that doesn't exist in the result type, 61 | @ShowList@. 62 | 63 | - We constraint that @a@ value with the 'Show' class. 64 | 65 | This is a rather contrived example, and better ones will emerge as we get 66 | further through the exercises, so bear with me. In order to build a data type 67 | that accurately captures our requirement, we need to /generalise/ the notion 68 | of an algebraic data type. Luckily, a well-named solution exists: GADTs, or 69 | generalised algebraic data types! 70 | -} 71 | 72 | data ShowList where 73 | ShowNil :: ShowList 74 | ShowCons :: Show a => a -> ShowList -> ShowList 75 | 76 | {- 77 | Notice here that we've written /exactly/ the functions we wanted. We've said 78 | that a 'ShowList' is either a 'ShowNil', /or/ a 'ShowCons' of some @a@ type 79 | and some other 'ShowList'. 80 | 81 | When we come to write an instance for 'Show', we'll pattern-match just as we 82 | did before. However, something is different this time: when we pattern-match 83 | on @a@, we actually have no idea what its type is! Because it's not in the 84 | result type, we have no information to figure it out. We can know the @a@ 85 | inside a @List a@ by looking at the type, but this type doesn't have @a@ in 86 | it! 87 | 88 | In fact, from the available information, all we can actually know is that it 89 | has a 'Show' instance. Beyond that, it could be /anything/. Luckily, that's 90 | the only thing we actually need: 91 | -} 92 | 93 | instance Show ShowList where 94 | show ShowNil = "[]" 95 | show (ShowCons head tail) = show head ++ " : " ++ show tail 96 | 97 | {- 98 | Note that the implementation is /identical/ to 'List'! The only difference 99 | here is that our list's inhabitants don't have to be the same type. As long 100 | as our type has a 'Show' instance, we can add it to the list: 101 | -} 102 | 103 | example2 :: String 104 | example2 = show (ShowCons "Tom" (ShowCons 25 (ShowCons True ShowNil))) 105 | -- This is the "do you like dogs?" flag :) ^ 106 | 107 | {- 108 | This time, we get "\"Tom\" : 25 : True : []". All our types are different, but 109 | they all have 'Show' instances, and that's enough to write a 'Show' instance 110 | for the whole list. 111 | 112 | In more technical terms, we say the @a@ in our 'ShowCons' constructor is an 113 | /existential/ variable: it only exists within the scope of the constructing 114 | function. Outside of that, we have no idea what it could be! All we know 115 | about it is what's included in the context of the GADT. When you pattern- 116 | match on the constructor, as we did in its 'Show' instance, we get two 117 | variables in scope: 118 | 119 | @ 120 | head :: Show a => a 121 | tail :: ShowList 122 | @ 123 | 124 | We don't know what @a@ is. If you try to do something with it like (+1), 125 | you'll get a type error saying that we can't deduce a 'Num' instance for @a@. 126 | If you try to call @head == head@, you'll get an error saying we can't deduce 127 | an 'Eq' instance. We know /nothing/ about this type, other than that it has 128 | an instance of 'Show'. As a result, we can only call it with things that work 129 | for any 'Show' type. In practical terms, this boils down to one function (and 130 | any function built on top of it): 'show'. All we can do with this type is 131 | 'show' it. Once we've done that, we have a String, and we can do anything we 132 | could normally do to a string, but that's it! To work with an /existential/ 133 | variable, we have to use functions that work /for all/ its possible types. 134 | -} 135 | 136 | -- showListHead :: Show a => ShowList -> Maybe a 137 | -- showListHead ShowNil = Nothing 138 | -- showListHead (ShowCons head _) = Just head 139 | 140 | {- 141 | Uncomment the above function - can you see why it doesn't work? 142 | 143 | Here, we're saying something /slightly/ different to what we want: we're 144 | saying that the caller gets to "pick" what @a@ is. For example, I could write 145 | something like: 146 | 147 | @ 148 | test :: ShowList -> Maybe Int 149 | test = showListHead 150 | @ 151 | 152 | Note that 'Int' is a type that implements 'Show', so, if 'showListHead' 153 | compiled, this would work - as the person calling this function, I've picked 154 | @a@ to be 'Int'. This idea of who "picks" types will be covered later on when 155 | we cover higher-rank polymorphism. For now, all we can do is show the head: 156 | -} 157 | 158 | showListHead' :: ShowList -> Maybe String 159 | showListHead' ShowNil = Nothing 160 | showListHead' (ShowCons head _) = Just (show head) 161 | -------------------------------------------------------------------------------- /02-FlexibleInstances/exercise02.cabal: -------------------------------------------------------------------------------- 1 | name: exercise02 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: FlexibleInstances 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /02-FlexibleInstances/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | module Exercises where 2 | 3 | class PopQuiz a 4 | 5 | -- | Which of the following instances require 'FlexibleInstances'? Don't cheat 6 | -- :D This is a tricky one, but look out for nested concrete types! 7 | 8 | -- instance PopQuiz Bool 9 | -- instance PopQuiz [Bool] 10 | -- instance PopQuiz [a] 11 | -- instance PopQuiz (a, b) 12 | -- instance PopQuiz [(a, b)] 13 | -- instance PopQuiz (IO a) 14 | 15 | newtype RIO r a = RIO (r -> IO a) -- Remember, this is a /new type/. 16 | type RIO' r a = r -> IO a 17 | 18 | -- instance PopQuiz (RIO Int a) 19 | -- instance PopQuiz (RIO r a) 20 | -- instance PopQuiz (RIO' r a) 21 | -- instance PopQuiz (r -> IO a) 22 | -- instance PopQuiz (a -> b) -- We can write (a -> b) as ((->) a b). 23 | -- instance PopQuiz (a -> b -> c) 24 | -- instance PopQuiz (a, b, c) 25 | -- instance PopQuiz (a, (b, c)) 26 | -- instance PopQuiz () 27 | -- instance PopQuiz (a, b, c, a) 28 | 29 | data Pair a = Pair a a 30 | type Pair' a = (a, a) 31 | 32 | -- instance PopQuiz (a, a) 33 | -- instance PopQuiz (Pair a) 34 | -- instance PopQuiz (Pair' a) 35 | -------------------------------------------------------------------------------- /02-FlexibleInstances/src/FlexibleInstances.hs: -------------------------------------------------------------------------------- 1 | {- Now that we're experts in GADTs, today's -} module {- will be a /very/ short 2 | rationale for -} FlexibleInstances {- and -} where {- we need them. -} 3 | 4 | --- 5 | 6 | {- 7 | Here is a /really/ underwhelming class with no functions or goodies. It has 8 | one parameter, @a@, and that's it. 9 | -} 10 | 11 | class SomeClass a 12 | 13 | {- 14 | Now, it will come as no surprise that we can write an instance for any type 15 | we can imagine: 16 | -} 17 | 18 | instance SomeClass Bool 19 | instance SomeClass Int 20 | -- instance SomeClass String 21 | 22 | {- 23 | If you uncomment that last one, you'll see that GHC gets upset. Specifically, 24 | it's upset because we are writing an instance for 'String', but 'String' is a 25 | type synonym for '[Char]'. However, it has a solution to suggest to us: 26 | 27 | {-# LANGUAGE TypeSynonymInstances #-} 28 | 29 | This extension lets us write instances for type synonyms, which GHC can then 30 | expand for us, meaning that our instance for 'String' will be expanded to 31 | '[Char]'. Problem solved, right? Well, not quite... 32 | 33 | Remove the extension*, re-comment that instance, and let's imagine we gave up 34 | and wrote an instance for [Char]: 35 | 36 | * It just so happens that turning on 'FlexibleInstances' will also turn on 37 | 'TypeSynonymInstances' as an /implied extension/, so you'll probably never 38 | actually need this extension explicitly. 39 | -} 40 | 41 | -- instance SomeClass [Char] 42 | 43 | {- 44 | If you uncomment /this/ line, you'll see the error we saw with 'String' once 45 | we enabled @TypeSynonymInstances@ (although now the error says '[Char]' 46 | instead of 'String'. Let's take a look: 47 | 48 | • Illegal instance declaration for ‘SomeClass [Char]’ 49 | (All instance types must be of the form (T a1 ... an) 50 | where a1 ... an are *distinct type variables*, 51 | and each type variable appears at most once in the instance head. 52 | Use FlexibleInstances if you want to disable this.) 53 | • In the instance declaration for ‘SomeClass [Char]’ 54 | 55 | This is actually one of GHC's more helpful extension errors. What this says 56 | is that our instances must all be for types, which it calls 'T'. If those 57 | types have type parameters, all the parameters must be /variables/ – none of 58 | them can be concrete values – and all of them must be different. 59 | 60 | Below are a few examples that work. Notice that they're all some type 61 | constructor applied to a number of variables: 62 | -} 63 | 64 | instance SomeClass [a] -- One variable 65 | instance SomeClass (Either e a) -- Two variables 66 | instance SomeClass () -- No variables! 67 | 68 | {- 69 | Conversely, here are some that don't work: 70 | -} 71 | 72 | -- The 'Maybe' type has a parameter that isn't a variable! 73 | 74 | -- instance SomeClass (Maybe Bool) 75 | 76 | 77 | -- The variables are not unique. 78 | 79 | -- instance SomeClass (Either e e) 80 | 81 | 82 | -- The variables are unique, but one of 'Either''s parameters isn't a variable: 83 | -- @Maybe a@ is a type that /contains/ a variable, which fails GHC's check. It 84 | -- has to be a type variable, with no exceptions. 85 | 86 | -- instance SomeClass (Either e (Maybe a)) 87 | 88 | {- 89 | That literally is it. The 'FlexibleInstances' extension lifts these 90 | restrictions for us: 91 | 92 | - We can now use type synonyms in instance definitions. This can be super 93 | helpful when you're writing an instance for a big, ugly type. 94 | 95 | - We can write instances that enforce equality between types (such as writing 96 | an instance for 'Either e e' - we'll only have an instance when the left 97 | and right types are the same!) 98 | 99 | - We can specify arguments to types that /aren't/ type variables. In other 100 | words, we can write instances for types with other types in their 101 | parameters. This is super useful, as we can now write different instances 102 | for, say, 'Either Int a' and 'Either String a', whereas we were unable 103 | without 'FlexibleInstances' - both would just be 'Either e a'! 104 | -} 105 | -------------------------------------------------------------------------------- /03-KindSignatures/exercise03.cabal: -------------------------------------------------------------------------------- 1 | name: exercise03 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: KindSignatures 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /03-KindSignatures/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE KindSignatures #-} 3 | module Exercises where 4 | 5 | import Data.Kind (Constraint, Type) 6 | 7 | -- | Fix the following classes by annotating them with kind signatures. GHC 8 | -- will tell you exactly what the problem is, so be sure to try /before/ 9 | -- uncommenting! 10 | 11 | 12 | 13 | 14 | 15 | {- ONE -} 16 | 17 | class Question1 a 18 | -- instance Question1 Maybe 19 | 20 | 21 | 22 | 23 | 24 | {- TWO -} 25 | 26 | class Question2 a 27 | -- instance Question2 Either 28 | 29 | 30 | 31 | 32 | 33 | {- THREE -} 34 | 35 | class Question3 a 36 | -- instance Question3 (,,) 37 | 38 | 39 | 40 | 41 | 42 | {- FOUR -} 43 | 44 | class Question4 a 45 | -- instance Question4 (->) 46 | 47 | 48 | 49 | 50 | 51 | {- FIVE -} 52 | 53 | class Question5 a 54 | -- instance Question5 [[Int]] 55 | 56 | 57 | 58 | 59 | 60 | {- SIX -} 61 | 62 | class Question6 a 63 | -- instance Question6 (Either () (Maybe (IO String))) 64 | 65 | 66 | 67 | 68 | 69 | {- SEVEN -} 70 | 71 | class Question7 a 72 | -- instance Question7 (Eq Int) -- This instance looks flexible... 73 | 74 | 75 | 76 | 77 | 78 | {- EIGHT -} 79 | 80 | class Question8 a 81 | -- instance Question8 Show 82 | 83 | 84 | 85 | 86 | 87 | {- NINE -} 88 | 89 | class Question9 a 90 | -- instance Question9 (Functor Maybe) 91 | 92 | 93 | 94 | 95 | 96 | {- TEN -} 97 | 98 | -- A kind error! Assume two out of three of these have the correct kind; what 99 | -- should it be? Which part is broken? What's wrong with it? 100 | 101 | class Question10 a 102 | -- instance Question10 (Monad m, Eq m, Show a) 103 | -------------------------------------------------------------------------------- /03-KindSignatures/src/KindSignatures.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE KindSignatures #-} 3 | 4 | module {- 3, in which our heroes discover -} KindSignatures {- and the ways in 5 | which they can help us. We'll see -} where {- we need them, and where they're 6 | just good documentation.. -} 7 | 8 | --- 9 | 10 | {- 11 | We are, hopefully, familiar with the notion of /types/: @3@ is a value of 12 | type @Int@, @Nothing@ is a value of @Maybe (IO String)@, and so on. However, 13 | there is also a notion of types /for types/: kinds. 14 | 15 | In our standard, everyday usage, all the types we use have the same kind: 16 | '*', which we'll pronounce 'Type'. In fact, we can use 'Type' instead of '*' 17 | by using the following import. 18 | -} 19 | 20 | import Data.Kind -- Type = (*) 21 | 22 | {- 23 | This is a good habit to adopt, as '*' is going to be deprecated soon, in 24 | favour of the more explicit and (hopefully) clear 'Type'. Now, we know all 25 | sorts of things of kind 'Type': 26 | -} 27 | 28 | class OfKindType a 29 | 30 | instance OfKindType Bool 31 | instance OfKindType String -- Which extension did we need 32 | instance OfKindType (Maybe (IO ())) -- to get /these/ to compile? 33 | 34 | {- 35 | We can start with an intuition that the 'Type' kind is inhabited by /things 36 | with a runtime value/. This isn't 100% accurate, but gives us somewhere to 37 | start: all our /values/ have a type that has a kind of 'Type'. We can see, 38 | for example, that @Maybe Int@ is of kind 'Type'. 'Maybe' is a funny one, 39 | though, right? We could think of it as a "type-level function": 40 | 41 | @ 42 | Maybe :: Type -> Type 43 | @ 44 | 45 | In other words, 'Maybe' on its own isn't a type - it's a type /constructor/. 46 | "A 'Maybe' of /what/?", the type-checker asks. 'Maybe Int' is a type, and 47 | 'Maybe ()' is a type, but 'Maybe' isn't. We can say that 'Maybe' has kind 48 | 'Type -> Type' because it's like a function that, given a type, gives us a 49 | type. For example, given 'Int', we get 'Maybe Int'. Given 'Bool', we get 50 | 'Maybe Bool', and so on. 51 | 52 | It is because of this that we can't write the following instance: 53 | -} 54 | 55 | -- instance OfKindType Maybe 56 | 57 | {- 58 | • Expecting one more argument to ‘Maybe’ 59 | Expected a type, but ‘Maybe’ has kind ‘* -> *’ 60 | • In the first argument of ‘OfKindType’, namely ‘Maybe’ 61 | In the instance declaration for ‘OfKindType Maybe’ 62 | 63 | The big problem here is that, in the absence of a better idea, GHC will 64 | assume that a typeclass parameter is of kind 'Type'. 65 | 66 | How do we tell it otherwise? 67 | 68 | One way is to give GHC more information about our type by filling in method 69 | signatures for our class. 70 | 71 | For example, let's take a look at a simplfied definition for the Functor class: 72 | -} 73 | 74 | class Functor f where 75 | fmap :: (a -> b) -> f a -> f b 76 | 77 | {- 78 | Although @f@ is on its own here in the class definition, if we take a closer 79 | look at the signature for @fmap@ we can see that @f@ never appears on its 80 | own, but it sits right in front of another type variable every time it shows 81 | up: @f a@ and @f b@. 82 | 83 | This information alone is enough for GHC to infer that the type of @f@ is 84 | actually ’* -> *’. 85 | 86 | Let's try another example: 87 | -} 88 | 89 | class Stuff a where 90 | thing :: a b c d 91 | 92 | {- 93 | Here, we can see @a@ has 3 more type variables right after it: @b@, @c@ and @d@. 94 | This means that @a@ will be given a kind of ’* -> * -> * -> *’. 95 | 96 | Another way to let GHC know this is by explicitly providing it with a kind 97 | signature in the class definition itself. 98 | 99 | This is exactly what the KindSignatures extension allows us to do: 100 | -} 101 | 102 | class OfKindTypeToType (a :: Type -> Type) 103 | 104 | {- 105 | Here, we've said that the /kind/ of the argument to this class must be a 106 | 'Type -> Type' argument. Of course, we actually know plenty of things with 107 | kind 'Type -> Type', even if we haven't thought about it before: 108 | -} 109 | 110 | instance OfKindTypeToType Maybe 111 | instance OfKindTypeToType [] 112 | instance OfKindTypeToType (Either e) -- What is the kind of 'Either'? 113 | instance OfKindTypeToType ((,) a) 114 | instance OfKindTypeToType IO 115 | 116 | {- 117 | Try deleting the kind signature from the 'OfKindTypeToType' class, and see 118 | that we end up with kind errors for every instance above. We'll see later on 119 | when we discuss ConstraintKinds and DataKinds that this is one of the 120 | extensions we'll turn on /every time/ we want to do something a little 121 | complex. I also think it's a nice one simply for the sake of documentation: 122 | -} 123 | 124 | class MyFavouriteBifunctor (element :: (Type -> Type -> Type)) 125 | instance MyFavouriteBifunctor Either 126 | instance MyFavouriteBifunctor (,) 127 | 128 | 129 | {- 130 | So, right now, we can think of all our kinds as being 'Type' or @a -> b@ for 131 | some kinds @a@ and @b@, and that's it. However, there's one more that is 132 | worth mentioning: 'Constraint'. This is the kind of constraints: 133 | -} 134 | 135 | class Whoa (constraint :: Constraint) -- Also from Data.Kind 136 | instance Whoa (Eq Int) 137 | instance Whoa (Show String) 138 | instance Whoa () 139 | 140 | {- 141 | That last one might look a bit odd - isn't '()' a type? Shouldn't GHC 142 | complain that 'Type' and 'Constraint' are different kinds? Well, in true 143 | Haskell fashion, '()' is actually overloaded: in the 'Constraint' kind, it 144 | refers to the "empty constraint" - a constraint that is always true. The 145 | tuple syntax isn't actually that unusual when we think about it: 146 | 147 | @ 148 | f :: (Show a, Eq a) => a -> a -> String 149 | @ 150 | 151 | 'Constraint' is the kind of every member on the left side of the fat arrow. 152 | Sometimes, we just have a single constraint. Sometimes, we have several. In 153 | order to have several, we use the tuple syntax. In case you're interested: 154 | 155 | @ 156 | g :: () => a -> a 157 | g = id 158 | @ 159 | 160 | This constraint is always satisfied, so it's not something we see a lot until 161 | we get to constraint programming (which we'll cover when we get to 162 | TypeFamilies and ConstraintKinds). 163 | -} 164 | -------------------------------------------------------------------------------- /04-DataKinds/exercise04.cabal: -------------------------------------------------------------------------------- 1 | name: exercise04 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: DataKinds 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /04-DataKinds/src/DataKinds.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE GADTs #-} 4 | {-# LANGUAGE KindSignatures #-} 5 | 6 | {- Today's -} module {- will focus on one of my favourite GHC magic tricks: the 7 | -} DataKinds {- extension. We'll see how and -} where {- to use it to enhance 8 | GADTs in particular, but there are plenty more cases to follow! -} 9 | 10 | --- 11 | 12 | import Data.Kind (Constraint, Type) 13 | import Prelude hiding (zip) 14 | 15 | {- 16 | Now we know about flexible instances, kind signatures, and GADTs, we have a 17 | pretty solid base to start talking about data kinds. Recall that all types we 18 | define have kind 'Type': 19 | -} 20 | 21 | data Natural = Zero | Successor Natural 22 | 23 | {- 24 | This is a type with two constructors, 'Zero' - which we'll use to represent 25 | zero - and 'Successor' - which we'll use to represent "one more" than its 26 | argument, another natural number. Let's write everything out: 27 | 28 | - The 'Natural' type has kind 'Type'. 29 | - The 'Zero' constructor has type 'Natural'. 30 | - The 'Successor' constructor has type 'Natural -> Natural'. 31 | 32 | 'Successor' takes an argument, another natural, and returns us a natural that 33 | is one more. 34 | 35 | Recall also that there are other kinds available to us: 36 | -} 37 | 38 | class ConstraintKind (a :: Constraint) 39 | instance ConstraintKind (Eq Int) 40 | 41 | class TypeToType (a :: Type -> Type) 42 | instance TypeToType Maybe 43 | instance TypeToType (Either Int) 44 | 45 | {- 46 | The @DataKinds@ extension /extends/ the behaviour of data constructors. Now, 47 | whenever we introduce a data type like 'Natural', we also introduce a new 48 | kind called 'Natural'. Similarly, we introduce two /type constructors/: 49 | 50 | - 'Zero :: Natural 51 | - 'Successor :: Natural -> Natural 52 | 53 | Let's write everything out again. 54 | 55 | - 'Natural' is now a kind. 56 | - The @'Zero@ constructor has kind 'Natural'. 57 | - The @'Successor@ constructor has kind 'Natural -> Natural'. 58 | 59 | @'Successor@ is a /type/ of /kind/ @Natural -> Natural@, as it takes another 60 | type of kind 'Natural' as an argument. We say that this is a /promoted/ data 61 | type, as it now exists at both the value level and the type level. We use the 62 | apostrophe (usually called a "tick") before the type to distinguish between 63 | its value- and type-level versions. 64 | -} 65 | 66 | class NaturalKind (a :: Natural) 67 | instance NaturalKind 'Zero 68 | instance NaturalKind ('Successor 'Zero) 69 | instance NaturalKind ('Successor ('Successor 'Zero)) 70 | 71 | {- 72 | A thing worth mentioning here is that things of kind 'Natural' /don't have 73 | any values/. They /only/ exist at the type-level. There is no value that has 74 | type @'Zero@. With that in mind, you might be forgiven for wondering what the 75 | point is. The power of data kinds is best demonstrated when we use them for 76 | /phantom type parameters/ (which, remember, /also/ don't have a value 77 | attached!): 78 | -} 79 | 80 | data Vector (length :: Natural) (a :: Type) where 81 | VNil :: Vector 'Zero a 82 | VCons :: a -> Vector n a -> Vector ('Successor n) a 83 | 84 | {- 85 | What we have here is a list, as we have seen before, but with a twist: the 86 | type of this list (which we'll call a "vector") contains the length of the 87 | list! Now, why would we want this? Well, recall the magic of the exhaustivity 88 | checker when we use GADTs: 89 | -} 90 | 91 | head :: Vector ('Successor n) a -> a 92 | head (VCons head _) = head 93 | 94 | {- 95 | 'head' is a /total/ function. We don't have to match on the 'VNil' case 96 | because we could never construct a 'VNil' whose @length@ parameter is a 97 | @'Successor@! There's no need for 'Maybe' because it /can't/ go wrong at 98 | run-time. Why stop there? Let's do something /really/ clever: 99 | -} 100 | 101 | zip :: Vector n a -> Vector n b -> Vector n (a, b) 102 | zip VNil VNil = VNil 103 | zip (VCons x xs) (VCons y ys) = VCons (x, y) (zip xs ys) 104 | 105 | {- 106 | Now, if you've written 'zip' before, you'll be used to seeing /four/ cases on 107 | which to pattern-match: nil/nil, cons/nil, nil/cons, cons/cons. However, 108 | we've said in the type that our vectors are of the same length: by saying the 109 | first one is 'VNil', the type-checker /knows/ the second must also be the 110 | same. Similarly for the 'VCons' case: the type-checker /knows/ they must both 111 | be 'VCons' and their tails must be the same length. 112 | 113 | The type-checker can do a /lot/ with this little bit of extra information, 114 | and we can express some much more powerful concepts. We'll see later on that, 115 | with some pretty simple extensions (such as @TypeOperators@), @DataKinds@ is 116 | the first step towards an extremely rich type-level vocabulary for us to 117 | misuse! 118 | -} 119 | -------------------------------------------------------------------------------- /04-DataKinds/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE KindSignatures #-} 4 | module Exercises where 5 | 6 | import Data.Kind (Type) 7 | import Data.Function ((&)) 8 | 9 | 10 | 11 | 12 | 13 | {- ONE -} 14 | 15 | -- | One of the restrictions around classes that we occasionally hit is that we 16 | -- can only have one instance for a type. There are, for example, two good 17 | -- candidates for a monoid instance when we think about 'Integer': 18 | 19 | data IntegerMonoid = Sum | Product 20 | 21 | -- | a. Write a newtype around 'Integer' that lets us choose which instance we 22 | -- want. 23 | 24 | -- | b. Write the two monoid instances for 'Integer'. 25 | 26 | -- | c. Why do we need @FlexibleInstances@ to do this? 27 | 28 | 29 | 30 | 31 | 32 | {- TWO -} 33 | 34 | -- | We can write a type that /is/ of kind 'Type', but has no value-level 35 | -- members. We usually call this type 'Void': 36 | 37 | data Void -- No constructors! 38 | 39 | -- | a. If we promote this with DataKinds, can we produce any /types/ of kind 40 | -- 'Void'? 41 | 42 | -- | b. What are the possible type-level values of kind 'Maybe Void'? 43 | 44 | -- | c. Considering 'Maybe Void', and similar examples of kinds such as 45 | -- 'Either Void Bool', why do you think 'Void' might be a useful kind? 46 | 47 | 48 | 49 | 50 | 51 | {- THREE -} 52 | 53 | -- | a. Write a GADT that holds strings or integers, and keeps track of how 54 | -- many strings are present. Note that you might need more than 'Nil' and 55 | -- 'Cons' this time... 56 | 57 | data Nat = Z | S Nat 58 | 59 | data StringAndIntList (stringCount :: Nat) where 60 | -- ... 61 | 62 | -- | b. Update it to keep track of the count of strings /and/ integers. 63 | 64 | -- | c. What would be the type of the 'head' function? 65 | 66 | 67 | 68 | 69 | 70 | {- FOUR -} 71 | 72 | -- | When we talked about GADTs, we discussed existentials, and how we could 73 | -- only know something about our value if the context told us: 74 | 75 | data Showable where 76 | Showable :: Show a => a -> Showable 77 | 78 | -- | a. Write a GADT that holds something that may or may not be showable, and 79 | -- stores this fact in the type-level. 80 | 81 | data MaybeShowable (isShowable :: Bool) where 82 | -- ... 83 | 84 | -- | b. Write a 'Show' instance for 'MaybeShowable'. Your instance should not 85 | -- work unless the type is actually 'show'able. 86 | 87 | -- | c. What if we wanted to generalise this to @Constrainable@, such that it 88 | -- would work for any user-supplied constraint of kind 'Constraint'? How would 89 | -- the type change? What would the constructor look like? Try to build this 90 | -- type - GHC should tell you exactly which extension you're missing. 91 | 92 | 93 | 94 | 95 | 96 | {- FIVE -} 97 | 98 | -- | Recall our list type: 99 | 100 | data List a = Nil | Cons a (List a) 101 | 102 | -- | a. Use this to write a better 'HList' type than we had in the @GADTs@ 103 | -- exercise. Bear in mind that, at the type-level, 'Nil' and 'Cons' should be 104 | -- "ticked". Remember also that, at the type-level, there's nothing weird about 105 | -- having a list of types! 106 | 107 | data HList (types :: List Type) where 108 | -- HNil :: ... 109 | -- HCons :: ... 110 | 111 | -- | b. Write a well-typed, 'Maybe'-less implementation for the 'tail' function 112 | -- on 'HList'. 113 | 114 | -- | c. Could we write the 'take' function? What would its type be? What would 115 | -- get in our way? 116 | 117 | 118 | 119 | 120 | 121 | {- SIX -} 122 | 123 | -- | Here's a boring data type: 124 | 125 | data BlogAction 126 | = AddBlog 127 | | DeleteBlog 128 | | AddComment 129 | | DeleteComment 130 | 131 | -- | a. Two of these actions, 'DeleteBlog' and 'DeleteComment', should be 132 | -- admin-only. Extend the 'BlogAction' type (perhaps with a GADT...) to 133 | -- express, at the type-level, whether the value is an admin-only operation. 134 | -- Remember that, by switching on @DataKinds@, we have access to a promoted 135 | -- version of 'Bool'! 136 | 137 | -- | b. Write a 'BlogAction' list type that requires all its members to be 138 | -- the same "access level": "admin" or "non-admin". 139 | 140 | -- data BlogActionList (isSafe :: ???) where 141 | -- ... 142 | 143 | -- | c. Let's imagine that our requirements change, and 'DeleteComment' is now 144 | -- available to a third role: moderators. Could we use 'DataKinds' to introduce 145 | -- the three roles at the type-level, and modify our type to keep track of 146 | -- this? 147 | 148 | 149 | 150 | 151 | 152 | {- SEVEN -} 153 | 154 | -- | When we start thinking about type-level Haskell, we inevitably end up 155 | -- thinking about /singletons/. Singleton types have a one-to-one value-type 156 | -- correspondence - only one value for each type, only one type for each value. 157 | -- A simple example is '()', whose only value is '()'. 'Bool' is /not/ a 158 | -- singleton, because it has multiple values. 159 | 160 | -- We can, however, /build/ a singleton type for 'Bool': 161 | 162 | data SBool (value :: Bool) where 163 | SFalse :: SBool 'False 164 | STrue :: SBool 'True 165 | 166 | -- | a. Write a singleton type for natural numbers: 167 | 168 | data SNat (value :: Nat) where 169 | -- ... 170 | 171 | -- | b. Write a function that extracts a vector's length at the type level: 172 | 173 | length :: Vector n a -> SNat n 174 | length = error "Implement me!" 175 | 176 | -- | c. Is 'Proxy' a singleton type? 177 | 178 | data Proxy a = Proxy 179 | 180 | 181 | 182 | 183 | 184 | {- EIGHT -} 185 | 186 | -- | Let's imagine we're writing some Industry Haskell™, and we need to read 187 | -- and write to a file. To do this, we might write a data type to express our 188 | -- intentions: 189 | 190 | data Program result 191 | = OpenFile (Program result) 192 | | WriteFile String (Program result) 193 | | ReadFile (String -> Program result) 194 | | CloseFile ( Program result) 195 | | Exit result 196 | 197 | -- | We could then write a program like this to use our language: 198 | 199 | myApp :: Program Bool 200 | myApp 201 | = OpenFile $ WriteFile "HEY" $ (ReadFile $ \contents -> 202 | if contents == "WHAT" 203 | then WriteFile "... bug?" $ Exit False 204 | else CloseFile $ Exit True) 205 | 206 | -- | ... but wait, there's a bug! If the contents of the file equal "WHAT", we 207 | -- forget to close the file! Ideally, we would like the compiler to help us: we 208 | -- could keep track of whether the file is open at the type level! 209 | -- 210 | -- - We should /not/ be allowed to open a file if another file is currently 211 | -- open. 212 | -- 213 | -- - We should /not/ be allowed to close a file unless a file is open. 214 | -- 215 | -- If we had this at the type level, the compiler should have been able to tell 216 | -- us that the branches of the @if@ have different types, and this program 217 | -- should never have made it into production. We should also have to say in the 218 | -- type of 'myApp' that, once the program has completed, the file will be 219 | -- closed. 220 | 221 | -- | Improve the 'Program' type to keep track of whether a file is open. Make 222 | -- sure the constructors respect this flag: we shouldn't be able to read or 223 | -- write to the file unless it's open. This exercise is a bit brain-bending; 224 | -- why? How could we make it more intuitive to write? 225 | 226 | -- | EXTRA: write an interpreter for this program. Nothing to do with data 227 | -- kinds, but a nice little problem. 228 | 229 | interpret :: Program {- ??? -} a -> IO a 230 | interpret = error "Implement me?" 231 | 232 | 233 | 234 | 235 | 236 | {- NINE -} 237 | 238 | -- | Recall our vector type: 239 | 240 | data Vector (n :: Nat) (a :: Type) where 241 | VNil :: Vector 'Z a 242 | VCons :: a -> Vector n a -> Vector ('S n) a 243 | 244 | -- | Imagine we want to write the '(!!)' function for this vector. If we wanted 245 | -- to make this type-safe, and avoid 'Maybe', we'd have to have a type that can 246 | -- only hold numbers /smaller/ than some type-level value. 247 | 248 | -- | a. Implement this type! This might seem scary at first, but break it down 249 | -- into Z and S cases. That's all the hint you need :) 250 | 251 | data SmallerThan (limit :: Nat) where 252 | -- ... 253 | 254 | -- | b. Write the '(!!)' function: 255 | 256 | (!!) :: Vector n a -> SmallerThan n -> a 257 | (!!) = error "Implement me!" 258 | 259 | -- | c. Write a function that converts a @SmallerThan n@ into a 'Nat'. 260 | -------------------------------------------------------------------------------- /05-RankNTypes/exercise05.cabal: -------------------------------------------------------------------------------- 1 | name: exercise05 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: RankNTypes 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /05-RankNTypes/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE KindSignatures #-} 3 | {-# LANGUAGE GADTs #-} 4 | {-# LANGUAGE RankNTypes #-} 5 | module Exercises where 6 | 7 | import Data.Kind (Type) 8 | 9 | 10 | 11 | 12 | 13 | {- ONE -} 14 | 15 | -- | The following GADT creates a list of values of unknown types: 16 | 17 | data Exlistential where 18 | Nil :: Exlistential 19 | Cons :: a -> Exlistential -> Exlistential 20 | 21 | -- | a. Write a function to "unpack" this exlistential into a list. 22 | 23 | -- unpackExlistential :: Exlistential -> (forall a. a -> r) -> [r] 24 | -- unpackExlistential = error "Implement me!" 25 | 26 | -- | b. Regardless of which type @r@ actually is, what can we say about the 27 | -- values in the resulting list? 28 | 29 | -- | c. How do we "get back" knowledge about what's in the list? Can we? 30 | 31 | 32 | 33 | 34 | 35 | {- TWO -} 36 | 37 | -- | Consider the following GADT that existentialises a 'Foldable' structure 38 | -- (but, crucially, not the type inside). 39 | 40 | data CanFold a where 41 | CanFold :: Foldable f => f a -> CanFold a 42 | 43 | -- | a. The following function unpacks a 'CanFold'. What is its type? 44 | 45 | -- unpackCanFold :: ??? 46 | -- unpackCanFold f (CanFold x) = f x 47 | 48 | -- | b. Can we use 'unpackCanFold' to figure out if a 'CanFold' is "empty"? 49 | -- Could we write @length :: CanFold a -> Int@? If so, write it! 50 | 51 | -- | c. Write a 'Foldable' instance for 'CanFold'. Don't overthink it. 52 | 53 | 54 | 55 | 56 | 57 | {- THREE -} 58 | 59 | -- | Recall our existential 'EqPair' GADT: 60 | 61 | data EqPair where 62 | EqPair :: Eq a => a -> a -> EqPair 63 | 64 | -- | a. Write a function that "unpacks" an 'EqPair' by applying a user-supplied 65 | -- function to its pair of values in the existential type. 66 | 67 | -- | b. Write a function that takes a list of 'EqPair's and filters it 68 | -- according to some predicate on the unpacked values. 69 | 70 | -- | c. Write a function that unpacks /two/ 'EqPair's. Now that both our 71 | -- variables are in rank-2 position, can we compare values from different 72 | -- pairs? 73 | 74 | 75 | 76 | 77 | 78 | {- FOUR -} 79 | 80 | -- | When I was building @purescript-panda@, I came across a neat use case for 81 | -- rank-2 types. Consider the following sketch of a type: 82 | 83 | data Component input output 84 | -- = Some sort of component stuff. 85 | 86 | -- | Now, let's imagine we want to add a constructor to "nest" a component 87 | -- inside another component type. We need a way of transforming between our 88 | -- "parent" I/O and "child" I/O, so we write this type: 89 | 90 | data Nested input output subinput suboutput 91 | = Nested 92 | { inner :: Component subinput suboutput 93 | , input :: input -> subinput 94 | , output :: suboutput -> output 95 | } 96 | 97 | -- | a. Write a GADT to existentialise @subinput@ and @suboutput@. 98 | 99 | data NestedX input output where 100 | -- ... 101 | 102 | -- | b. Write a function to "unpack" a NestedX. The user is going to have to 103 | -- deal with all possible @subinput@ and @suboutput@ types. 104 | 105 | -- | c. Why might we want to existentialise the subtypes away? What do we lose 106 | -- by doing so? What do we gain? 107 | 108 | -- In case you're interested in where this actually turned up in the code: 109 | -- https://github.com/i-am-tom/purescript-panda/blob/master/src/Panda/Internal/Types.purs#L84 110 | 111 | 112 | 113 | 114 | 115 | {- FIVE -} 116 | 117 | -- | Let's continue with the theme of the last question. Let's say I have a few 118 | -- HTML-renderable components: 119 | 120 | data FirstGo input output 121 | = FText String 122 | | FHTML (String, String) [FirstGo input output] 123 | -- ^ properties ^ children 124 | 125 | -- | This is fine, but there's an issue: some functions only really apply to 126 | -- 'FText' /or/ 'FHTML'. Now that this is a sum type, they'd have to result in 127 | -- a 'Maybe'! Let's avoid this by splitting this sum type into separate types: 128 | 129 | data Text = Text String 130 | -- data HTML = HTML { properties :: (String, String), children :: ??? } 131 | 132 | -- | Uh oh! What's the type of our children? It could be either! In fact, it 133 | -- could probably be anything that implements the following class, allowing us 134 | -- to render our DSL to an HTML string: 135 | class Renderable component where render :: component -> String 136 | 137 | -- | a. Write a type for the children. 138 | 139 | -- | b. What I'd really like to do when rendering is 'fmap' over the children 140 | -- with 'render'; what's stopping me? Fix it! 141 | 142 | -- | c. Now that we're an established Haskell shop, we would /also/ like the 143 | -- option to render our HTML to a Shakespeare template to write to a file 144 | -- (http://hackage.haskell.org/package/shakespeare). How could we support this 145 | -- new requirement with minimal code changes? 146 | 147 | 148 | 149 | 150 | 151 | {- SIX -} 152 | 153 | -- | Remember our good ol' mystery box? 154 | 155 | data MysteryBox a where 156 | EmptyBox :: MysteryBox () 157 | IntBox :: Int -> MysteryBox () -> MysteryBox Int 158 | StringBox :: String -> MysteryBox Int -> MysteryBox String 159 | BoolBox :: Bool -> MysteryBox String -> MysteryBox Bool 160 | 161 | -- | a. Knowing what we now know about RankNTypes, we can write an 'unwrap' 162 | -- function! Write the function, and don't be too upset if we need a 'Maybe'. 163 | 164 | -- | b. Why do we need a 'Maybe'? What can we still not know? 165 | 166 | -- | c. Write a function that uses 'unwrap' to print the name of the next 167 | -- layer's constructor. 168 | 169 | 170 | 171 | 172 | 173 | {- SEVEN -} 174 | 175 | -- | When we talked about @DataKinds@, we briefly looked at the 'SNat' type: 176 | 177 | data Nat = Z | S Nat 178 | 179 | data SNat (n :: Nat) where 180 | SZ :: SNat 'Z 181 | SS :: SNat n -> SNat ('S n) 182 | 183 | -- | We also saw that we could convert from an 'SNat' to a 'Nat': 184 | 185 | toNat :: SNat n -> Nat 186 | toNat = error "You should already know this one ;)" 187 | 188 | -- | How do we go the other way, though? How do we turn a 'Nat' into an 'SNat'? 189 | -- In the general case, this is impossible: the 'Nat' could be calculated from 190 | -- some user input, so we have no way of knowing what the 'SNat' type would be. 191 | -- However, if we could have a function that would work /for all/ 'SNat' 192 | -- values... 193 | 194 | -- | Implement the 'fromNat' function. It should take a 'Nat', along with some 195 | -- SNat-accepting function (maybe at a higher rank?) that returns an @r@, and 196 | -- then returns an @r@. The successor case is a bit weird here - type holes 197 | -- will help you! 198 | 199 | -- | If you're looking for a property that you could use to test your function, 200 | -- remember that @fromNat x toNat === x@! 201 | 202 | 203 | 204 | 205 | 206 | {- EIGHT -} 207 | 208 | -- | Bringing our vector type back once again: 209 | 210 | data Vector (n :: Nat) (a :: Type) where 211 | VNil :: Vector 'Z a 212 | VCons :: a -> Vector n a -> Vector ('S n) a 213 | 214 | -- | It would be nice to have a 'filter' function for vectors, but there's a 215 | -- problem: we don't know at compile time what the new length of our vector 216 | -- will be... but has that ever stopped us? Make it so! 217 | -------------------------------------------------------------------------------- /05-RankNTypes/src/RankNTypes.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE GADTs #-} 2 | {-# LANGUAGE RankNTypes #-} 3 | 4 | {- In Lou Bega's -} module {- number 5, we'll be discussing /higher rank 5 | polymorphism/ using the -} RankNTypes {- extension, and how a function can 6 | change entirely depending on -} where {- you put your quantifiers. -} 7 | 8 | {- 9 | Let's start things off with a function to wrap the values of a 'Tuple' in 10 | 'Maybe' by applying them both to 'Just': 11 | -} 12 | 13 | wrapMaybe (x, y) = (Just x, Just y) 14 | 15 | example :: (Maybe Int, Maybe String) 16 | example = wrapMaybe (25, "Tom") 17 | 18 | {- 19 | No problems so far: the 'Just' function takes a value of any type @a@, and 20 | returns a value of @Maybe a@. Now, this function is a bit specific: what we'd 21 | really like to do is _pass in_ the wrapping function that takes any type @a@ 22 | to a @f a@ for some @f@. No problem, we'll just introduce a couple more 23 | variables: 24 | -} 25 | 26 | wrapAny f (a, b) = (f a, f b) 27 | 28 | -- example2 :: (Maybe Int, Maybe String) 29 | -- example2 = wrapAny Just (25, "Tom") 30 | 31 | {- 32 | ... huh. The function type-checks, but the usage doesn't. Specifically, we 33 | get a type error: 34 | 35 | • Couldn't match type ‘[Char]’ with ‘Int’ 36 | Expected type: (Maybe Int, Maybe String) 37 | Actual type: (Maybe [Char], Maybe [Char]) 38 | • In the expression: wrapAny Just (25, "Tom") 39 | In an equation for ‘example2’: example2 = wrapAny Just (25, "Tom") 40 | 41 | Well, that's weird. Let's look at the type of 'wrapAny': 42 | 43 | >>> :t wrapAny 44 | wrapAny :: (a -> b) -> (a, a) -> (b, b) 45 | 46 | What happened?! 47 | 48 | The GHC type-checker looks in the body of the function, and sees that the 49 | same @f@ is used for both @a@ and @b@, and deduces that they must therefore 50 | be the same type. After all, how else could it work? 51 | 52 | ... The problem is, of course, we /know/ it could work - 'Just' /should/ 53 | work. The problem, it turns out, is a little bit subtle. 54 | 55 | By turning on RankNTypes*, we have also enabled ExplicitForAll, which allows 56 | us to write some syntax that may be familiar to PureScript users: 57 | -} 58 | 59 | id :: forall a. a -> a 60 | id x = x 61 | 62 | {- 63 | This definition says that 'id' will work /for all/ types that @a@ could 64 | possibly be. This is nothing special – GHC internally writes this @forall@ 65 | for us when we write a function like @id :: a -> a@ – so what's the big deal? 66 | Well, let's look at 'Just': 67 | -} 68 | 69 | just :: forall a. a -> Maybe a 70 | just = Just 71 | 72 | {- 73 | Cool. Nothing too surprising yet. Now, let's look at that 'wrapAny' again: 74 | -} 75 | 76 | wrapAny' :: forall a b. (a -> b) -> (a, a) -> (b, b) 77 | wrapAny' f (x, y) = (f x, f y) 78 | 79 | {- 80 | This is obviously not what we want: we want to introduce a new type 81 | parameter - @x@! 82 | -} 83 | 84 | -- wrapAny'' :: forall a b f x. (x -> f x) -> (a, b) -> (f a, f b) 85 | -- wrapAny'' f (x, y) = (f x, f y) 86 | 87 | {- 88 | Another type error! 89 | 90 | • Couldn't match expected type ‘x’ with actual type ‘a’ 91 | ‘a’ is a rigid type variable bound by 92 | the type signature for: 93 | wrapAny'' :: forall a b (f :: * -> *) x. 94 | (x -> f x) -> (a, b) -> (f a, f b) 95 | at src/RankNTypes.hs:83:1-63 96 | ‘x’ is a rigid type variable bound by 97 | the type signature for: 98 | wrapAny'' :: forall a b (f :: * -> *) x. 99 | (x -> f x) -> (a, b) -> (f a, f b) 100 | at src/RankNTypes.hs:83:1-63 101 | • In the first argument of ‘f’, namely ‘x’ 102 | In the expression: f x 103 | In the expression: (f x, f y) 104 | 105 | This definition /still/ doesn't type-check! Specifically, it's saying we 106 | can't unify @x@ with @a@, which means we can't call an @x -> f x@ function 107 | with an @a@ value. This makes sense. Imagine the following user call: 108 | -} 109 | 110 | -- wrapAnyExample :: (Maybe Int, Maybe String) 111 | -- wrapAnyExample = wrapAny (\x -> Just (x + 1)) (25, "Tom") 112 | 113 | {- 114 | Here, our users have chosen @f@ to be @Maybe@, @a@ to be @Int@, @b@ to be 115 | @String@, and @x@ to be @Int@ as well. There's the problem, though: we can't 116 | use an @Int -> Maybe Int@ function on a @String@! What /we/ need is a way of 117 | saying that the function must work /for all/ types that we put in. Wait... 118 | -} 119 | 120 | wrapAnyNew :: (forall x. x -> f x) -> (a, b) -> (f a, f b) 121 | wrapAnyNew f (x, y) = (f x, f y) 122 | 123 | {- 124 | Well, this seems to type-check, but... why? Well, let's think through it. 125 | Consider the following signatures: 126 | -} 127 | 128 | id' :: forall a. a -> a 129 | id' x = x 130 | 131 | const' :: forall a b. a -> b -> a 132 | const' x _ = x 133 | 134 | map' :: forall a b f. Functor f => (a -> b) -> (f a -> f b) 135 | map' = fmap 136 | 137 | {- 138 | In all these examples, the signatures say that they will work /for all/ 139 | types. In the case of map', we can give it an @a -> b@ and get back a 140 | function @f a -> f b@ /for all/ @a@, @b@, and @f@ types. What this means in 141 | practice is that we, as the caller, get to /choose/ what those types are. 142 | 143 | The function can't know anything more about them than what's in the context - in 144 | the case of @map'@, that it's a functor - because, beyond that, it could be 145 | /anything/. 146 | 147 | The problem with this is that the function never gets to choose one of the 148 | values for us. In the case of 'wrapAny', what it wants from /us/ is a 149 | polymorphic function that will, /for all/ types, lift them into some context 150 | like 'Maybe'. 151 | 152 | We need to give it a function that works /for all/ types so that /it/ can 153 | choose which types to pass in. That's why the argument has a @forall@! 154 | 155 | This who-gets-to-choose is a really good starting intuition for how ranks 156 | work: 157 | 158 | rank-0 Int -> String No one gets to choose. 159 | rank-1 forall a. a -> a Caller gets to choose. 160 | rank-2 forall a. a -> (forall b. b -> b) -> a Called gets to choose. 161 | 162 | This actually generalises, although we won't see many examples of functions 163 | with higher-ranked than 2. Use cases /do/ exist, however, if you're curious: 164 | 165 | https://stackoverflow.com/q/8405364 166 | 167 | For one last thought, let's think back to our existential GADTs: 168 | -} 169 | 170 | data Showable where 171 | Showable :: Show a => a -> Showable 172 | 173 | {- 174 | We said, to do anything with a 'Showable', we'd have to supply a function 175 | that worked /for all/ types implementing 'Show'. In other words, we could 176 | write an unpacking function: 177 | -} 178 | 179 | unpack :: Showable -> String 180 | unpack (Showable a) = show a 181 | 182 | {- 183 | Or, we could generalise it to take any relevant function. What might the type 184 | look like, you ask? Exactly as we'd imagine: 185 | -} 186 | 187 | unpack' :: (forall a. Show a => a -> r) -> Showable -> r 188 | unpack' f (Showable x) = f x 189 | 190 | {- 191 | We don't know what's in the 'Showable', we just know it has a 'Show' 192 | instance. So, in order to unpack it, we need a function that can express 193 | that! In reality, the first thing this function is going to do is 'show', but 194 | it won't always be that simple. The point is that we can use rank-2 to 195 | require that the given function works for /any/ type with a particular class' 196 | interface. 197 | 198 | This is the pattern you'll see in the overwhelming majority of examples with 199 | rank-2 types: we don't know what the value /is/, but we know about some 200 | constraint that it has. Get used to seeing signatures that look something 201 | like @X -> (forall a. Constraint a => a -> r) -> r@, because we'll be seeing 202 | a /lot/ of them. 203 | -} 204 | -------------------------------------------------------------------------------- /06-TypeFamilies/exercise06.cabal: -------------------------------------------------------------------------------- 1 | name: exercise06 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: TypeFamilies 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /06-TypeFamilies/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE TypeFamilies #-} 4 | {-# LANGUAGE TypeOperators #-} 5 | module Exercises where 6 | 7 | import Data.Kind (Constraint, Type) 8 | 9 | -- | Before we get started, let's talk about the @TypeOperators@ extension. All 10 | -- this does is allow us to write types whose names are operators, and write 11 | -- regular names as infix names with the backticks, as we would at the value 12 | -- level. 13 | 14 | 15 | 16 | 17 | 18 | {- ONE -} 19 | 20 | data Nat = Z | S Nat 21 | 22 | -- | a. Use the @TypeOperators@ extension to rewrite the 'Add' family with the 23 | -- name '+': 24 | 25 | -- | b. Write a type family '**' that multiplies two naturals using '(+)'. Which 26 | -- extension are you being told to enable? Why? 27 | 28 | data SNat (value :: Nat) where 29 | SZ :: SNat 'Z 30 | SS :: SNat n -> SNat ('S n) 31 | 32 | -- | c. Write a function to add two 'SNat' values. 33 | 34 | 35 | 36 | 37 | 38 | {- TWO -} 39 | 40 | data Vector (count :: Nat) (a :: Type) where 41 | VNil :: Vector 'Z a 42 | VCons :: a -> Vector n a -> Vector ('S n) a 43 | 44 | -- | a. Write a function that appends two vectors together. What would the size 45 | -- of the result be? 46 | 47 | -- append :: Vector m a -> Vector n a -> Vector ??? a 48 | 49 | -- | b. Write a 'flatMap' function that takes a @Vector n a@, and a function 50 | -- @a -> Vector m b@, and produces a list that is the concatenation of these 51 | -- results. This could end up being a deceptively big job. 52 | 53 | -- flatMap :: Vector n a -> (a -> Vector m b) -> Vector ??? b 54 | flatMap = error "Implement me!" 55 | 56 | 57 | 58 | 59 | 60 | {- THREE -} 61 | 62 | -- | a. More boolean fun! Write the type-level @&&@ function for booleans. 63 | 64 | -- | b. Write the type-level @||@ function for booleans. 65 | 66 | -- | c. Write an 'All' function that returns @'True@ if all the values in a 67 | -- type-level list of boleans are @'True@. 68 | 69 | 70 | 71 | 72 | 73 | {- FOUR -} 74 | 75 | -- | a. Nat fun! Write a type-level 'compare' function using the promoted 76 | -- 'Ordering' type. 77 | 78 | -- | b. Write a 'Max' family to get the maximum of two natural numbers. 79 | 80 | -- | c. Write a family to get the maximum natural in a list. 81 | 82 | 83 | 84 | 85 | 86 | {- FIVE -} 87 | 88 | data Tree = Empty | Node Tree Nat Tree 89 | 90 | -- | Write a type family to insert a promoted 'Nat' into a promoted 'Tree'. 91 | 92 | 93 | 94 | 95 | 96 | {- SIX -} 97 | 98 | -- | Write a type family to /delete/ a promoted 'Nat' from a promoted 'Tree'. 99 | 100 | 101 | 102 | 103 | 104 | {- SEVEN -} 105 | 106 | -- | With @TypeOperators@, we can use regular Haskell list syntax on the 107 | -- type-level, which I think is /much/ tidier than anything we could define. 108 | 109 | data HList (xs :: [Type]) where 110 | HNil :: HList '[] 111 | HCons :: x -> HList xs -> HList (x ': xs) 112 | 113 | -- | Write a function that appends two 'HList's. 114 | 115 | 116 | 117 | 118 | 119 | {- EIGHT -} 120 | 121 | -- | Type families can also be used to build up constraints. There are, at this 122 | -- point, a couple things that are worth mentioning about constraints: 123 | -- 124 | -- - As we saw before, '()' is the empty constraint, which simply has "no 125 | -- effect", and is trivially solved. 126 | -- 127 | -- - Unlike tuples, constraints are "auto-flattened": ((a, b), (c, (d, ())) is 128 | -- exactly equivalent to (a, b, c, d). Thanks to this property, we can build 129 | -- up constraints using type families! 130 | 131 | type family CAppend (x :: Constraint) (y :: Constraint) :: Constraint where 132 | CAppend x y = (x, y) 133 | 134 | -- | a. Write a family that takes a constraint constructor, and a type-level 135 | -- list of types, and builds a constraint on all the types. 136 | 137 | type family Every (c :: Type -> Constraint) (x :: [Type]) :: Constraint where 138 | -- ... 139 | 140 | -- | b. Write a 'Show' instance for 'HList' that requires a 'Show' instance for 141 | -- every type in the list. 142 | 143 | -- | c. Write an 'Eq' instance for 'HList'. Then, write an 'Ord' instance. 144 | -- Was this expected behaviour? Why did we need the constraints? 145 | 146 | 147 | 148 | 149 | 150 | {- NINE -} 151 | 152 | -- | a. Write a type family to calculate all natural numbers up to a given 153 | -- input natural. 154 | 155 | -- | b. Write a type-level prime number sieve. 156 | 157 | -- | c. Why is this such hard work? 158 | -------------------------------------------------------------------------------- /06-TypeFamilies/src/TypeFamilies.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE TypeFamilies #-} 4 | 5 | {- Today's -} module {- will be a very brief introduction to -} TypeFamilies {- 6 | and their uses. We're not going to go -} where {- Csongor's talk went, though 7 | there is plenty of further reading to be done if you're interested! -} 8 | 9 | import Data.Kind (Type) 10 | 11 | data Nat = Z | S Nat 12 | 13 | {- 14 | We're hopefully all aware of value-level functions: 15 | -} 16 | 17 | add :: Nat -> Nat -> Nat 18 | add Z y = y 19 | add (S x) y = S (add x y) 20 | 21 | {- 22 | Type families, by their simplest definition, are functions at the type-level. 23 | In case you're wondering, the @TypeFamilies@ extension implies the 24 | @KindSignatures@ extension: 25 | -} 26 | 27 | type family Add (x :: Nat) (y :: Nat) :: Nat where 28 | Add 'Z y = y 29 | Add ('S x) y = 'S (Add x y) 30 | 31 | {- 32 | The syntax is a bit funny, but hopefully the symmetry is obvious: we specify 33 | the parameters in the first line, as well as the return type, and then we 34 | write "equations" on the subsequent lines. Technically, this is a /closed/ 35 | type family: we specify all equations up front, like a value-level function. 36 | 37 | There are also open type families, which behave more like type classes, but 38 | we won't pay them too much attention: 39 | -} 40 | 41 | type family Open (x :: Type) :: Type 42 | 43 | type instance Open Int = String 44 | 45 | -- ... Loads more code and whatnot, maybe other files ... 46 | 47 | type instance Open (Maybe Bool) = IO Int 48 | 49 | {- 50 | So, why would this be useful? Well, we'll save the fun examples for the 51 | exercises, as most of these are pretty intuitive, but let's pick a nice, easy 52 | one. A while ago, we talked about a singleton boolean type: 53 | -} 54 | 55 | data SBool (value :: Bool) where 56 | STrue :: SBool 'True 57 | SFalse :: SBool 'False 58 | 59 | {- 60 | What if we wanted to "flip" the boolean value? What would the type signature 61 | be? As well as the value-level "not", we'd need a type-level equivalent to 62 | match! We can define this quite neatly as a type family: 63 | -} 64 | 65 | type family Not (input :: Bool) :: Bool where 66 | Not 'True = 'False 67 | Not 'False = 'True 68 | 69 | {- 70 | Now, we can write the function and its signature! 71 | -} 72 | 73 | not :: SBool input -> SBool (Not input) 74 | not STrue = SFalse 75 | not SFalse = STrue 76 | 77 | {- 78 | The @input@ type determines what the output type will be, and the type family 79 | will be evaluated for each pattern-match in our function. That's all there is 80 | to it! 81 | 82 | One last thing that may be of interest: type families don't necessarily have 83 | to be total. 84 | -} 85 | 86 | type family Subtract (x :: Nat) (y :: Nat) :: Nat where 87 | Subtract x 'Z = x 88 | Subtract ('S x) ('S y) = Subtract x y 89 | 90 | {- 91 | 'Subtract' subtracts the second value from the first. However, it doesn't 92 | deal with the posibility that the second value is /bigger/ than the first, 93 | because we don't have a way of representing negative numbers! So, what 94 | happens when we get it wrong? Let's load up ghci with @cabal repl@: 95 | 96 | > :set -XDataKinds 97 | > :kind! Subtract 'Z ('S 'Z) 98 | Subtract 'Z ('S 'Z) :: Nat 99 | = Subtract 'Z ('S 'Z) 100 | 101 | This is the first time we've played with ghci, so notice a couple things: 102 | 103 | - The @:set -X...@ syntax is how we turn on language extentsions within GHCi. 104 | 105 | - The @:kind ...@ command will tell us the kind of an extension, but the 106 | @:kind! ...@ command will tell us the normalised (simplified) form of the 107 | type. In our case, that means evaluating type families as far as we can. 108 | 109 | So, what happened? Well, if a we can't find an equation that matches our call 110 | to a type family, we can't reduce the expression, and it stays how it is. 111 | This is in part due to things like open type families - we might work out how 112 | to reduce the type family later on, so we can't definitely say it's an 113 | /error/... 114 | 115 | In this particular case, we say our expression is "stuck". We're not going to 116 | go into any further detail about what this means, but talk to Csongor if 117 | you're interested in the ways you can misuse this! 118 | -} 119 | -------------------------------------------------------------------------------- /07-ConstraintKinds/exercise07.cabal: -------------------------------------------------------------------------------- 1 | name: exercise07 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: ConstraintKinds 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /07-ConstraintKinds/src/ConstraintKinds.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ConstraintKinds #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE KindSignatures #-} 4 | 5 | {- Two of the -} module {- topics I found particularly difficult to place were 6 | -} ConstraintKinds {- and @PolyKinds@. I think as we explore concepts like 7 | MultiParamTypeClasses, it will become obvious why it's hard to say exactly 8 | -} where {- it belongs. Anyway, all that for later; let's go! -} 9 | 10 | import Data.Kind (Constraint, Type) 11 | 12 | {- 13 | In Haskell, we are hopefully all familiar with the idea of type parameters. 14 | For example, the @Maybe@ type has one, @a@: 15 | -} 16 | 17 | data Maybe a 18 | = Nothing 19 | | Just a 20 | 21 | {- 22 | We also saw in the earlier chapters that we can use kind signatures to 23 | parameterise different kinds: 24 | -} 25 | 26 | data TTProxy (x :: Type -> Type) 27 | = TTProxy 28 | 29 | eg0 :: TTProxy [] 30 | eg0 = TTProxy 31 | 32 | eg1 :: TTProxy IO 33 | eg1 = TTProxy 34 | 35 | data TTTProxy (x :: Type -> Type -> Type) 36 | = TTTProxy 37 | 38 | eg2 :: TTTProxy Either 39 | eg2 = TTTProxy 40 | 41 | eg3 :: TTTProxy (->) 42 | eg3 = TTTProxy 43 | 44 | {- 45 | We even saw that we could parameterise over constraints! 46 | -} 47 | 48 | data CProxy (x :: Constraint) 49 | = CProxy 50 | 51 | eg4 :: CProxy (Eq Int) 52 | eg4 = CProxy 53 | 54 | data TCProxy (x :: Type -> Constraint) 55 | = TCProxy 56 | 57 | eg5 :: TCProxy Eq 58 | eg5 = TCProxy 59 | 60 | {- 61 | Let's take this one step further. If we think about our original 62 | understanding of GADTs, we said that they allowed us to embed constraints 63 | within our data constructors. In that case, why can't we use a constraint 64 | parameter /as/ a constraint? 65 | 66 | This, dear reader, is the magic of ConstraintKinds! 67 | -} 68 | 69 | data HasConstraint (c :: Type -> Constraint) where 70 | Item :: c x => x -> HasConstraint c 71 | 72 | {- 73 | So, what's going on here? We've said that our type, 'HasConstraint', will be 74 | parameterised by some constraint @c@. We've said that, in order to construct 75 | a @HasConstraint c@, we must satisfy @c x@. The exciting thing here is that 76 | our constraint is polymorphic! 77 | 78 | Let's look at another example: @Dict@. 79 | -} 80 | 81 | data Dict (c :: Constraint) where 82 | Dict :: c => Dict c 83 | 84 | {- 85 | Dict is a constraint as a data type. True to the magic of a GADT, 86 | pattern-matching on a @Dict@ will bring its contents into scope. This allows 87 | us to write functions like this: 88 | -} 89 | 90 | eq :: Dict (Eq a) -> a -> a -> Bool 91 | eq Dict x y = x == y 92 | 93 | {- 94 | Perhaps interestingly, the following will fail to compile. This is because we 95 | haven't pattern-matched! Although there's only one possible value, we still 96 | need to pattern-match in order to bring the constraint evidence into scope. 97 | -} 98 | 99 | -- eq :: Dict (Eq a) -> a -> a -> Bool 100 | -- eq notPatternMatched x y = x == y 101 | -------------------------------------------------------------------------------- /07-ConstraintKinds/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ConstraintKinds #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE FlexibleInstances #-} 4 | {-# LANGUAGE GADTs #-} 5 | {-# LANGUAGE KindSignatures #-} 6 | {-# LANGUAGE RankNTypes #-} 7 | {-# LANGUAGE TypeFamilies #-} 8 | {-# LANGUAGE TypeOperators #-} 9 | module Exercises where -- ^ This is starting to look impressive, right? 10 | 11 | import Data.Kind (Constraint, Type) 12 | 13 | -- | Just a quick one today - there really isn't much to cover when we talk 14 | -- about ConstraintKinds, as it's hopefully quite an intuitive extension: we're 15 | -- just extending the set of things that we can use as constraints to include 16 | -- type parameters! 17 | 18 | 19 | 20 | 21 | 22 | {- ONE -} 23 | 24 | -- | Here's a super uninteresting list, which comes with an unfortunate 25 | -- restriction: everything in the list must have the same exact type. 26 | 27 | data List a = Nil | Cons a (List a) 28 | 29 | -- | We can generalise this data structure to a /constrained list/, in which we 30 | -- say, instead of "every value has this type", we say, "every value's type 31 | -- satisfies this constraint". 32 | 33 | -- | a. Do it! Think about the @Nil@ and @Cons@ cases separately; which 34 | -- constraints can the @Nil@ case satisfy? 35 | 36 | data ConstrainedList (c :: Type -> Constraint) where 37 | -- IMPLEMENT ME 38 | 39 | -- | b. Using what we know about RankNTypes, write a function to fold a 40 | -- constrained list. Note that we'll need a folding function that works /for 41 | -- all/ types who implement some constraint @c@. Wink wink, nudge nudge. 42 | 43 | -- foldConstrainedList :: ??? 44 | 45 | -- | Often, I'll want to constrain a list by /multiple/ things. The problem is 46 | -- that I can't directly write multiple constraints into my type, because the 47 | -- kind of @(Eq, Ord)@ isn't @Type -> Constraint@ - it's actually a kind error! 48 | 49 | -- | There is hope, however: a neat trick we can play is to define a new class, 50 | -- whose super classes are all the constraints we require. We can then write an 51 | -- instance for any a who satisfies these constraints. Neat, right? 52 | 53 | -- | c. Write this class instance so that we can have a constraint that 54 | -- combines `Monoid a` and `Show a`. What other extension did you need to 55 | -- enable? Why? 56 | 57 | -- class ??? => Constraints a 58 | -- instance ??? => Constraints a 59 | 60 | -- | What can we now do with this constrained list that we couldn't before? 61 | -- There are two opportunities that should stand out! 62 | 63 | 64 | 65 | 66 | 67 | {- TWO -} 68 | 69 | -- | Recall our HList: 70 | 71 | data HList (xs :: [Type]) where 72 | HNil :: HList '[] 73 | HCons :: x -> HList xs -> HList (x ': xs) 74 | 75 | -- | Ideally, we'd like to be able to fold over this list in some way, ideally 76 | -- by transforming every element into a given monoid according to the interface 77 | -- of some constraint. To do that, though, we'd need to know that everything in 78 | -- the list implemented a given constraint... if only we had a type family for 79 | -- this... 80 | 81 | -- | a. Write this fold function. I won't give any hints to the definition, but 82 | -- we will probably need to call it like this: 83 | 84 | -- test :: ??? => HList xs -> String 85 | -- test = fold (TCProxy :: TCProxy Show) show 86 | 87 | -- | b. Why do we need the proxy to point out which constraint we're working 88 | -- with? What does GHC not like if we remove it? 89 | 90 | -- | We typically define foldMap like this: 91 | 92 | foldMap :: Monoid m => (a -> m) -> [a] -> m 93 | foldMap f = foldr (\x acc -> f x <> acc) mempty 94 | 95 | -- | c. What constraint do we need in order to use our @(a -> m)@ function on 96 | -- an @HList@? You may need to look into the __equality constraint__ introduced 97 | -- by the @GADTs@ and @TypeFamilies@ extensions, written as @(~)@: 98 | 99 | -- * This tells GHC that @a@ and @b@ are equivalent. 100 | f :: a ~ b => a -> b 101 | f = id 102 | 103 | -- | Write @foldMap@ for @HList@! 104 | -------------------------------------------------------------------------------- /08-PolyKinds/exercise08.cabal: -------------------------------------------------------------------------------- 1 | name: exercise08 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: PolyKinds 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /08-PolyKinds/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE PolyKinds #-} 4 | {-# LANGUAGE TypeFamilies #-} 5 | {-# LANGUAGE TypeOperators #-} 6 | module Exercises where 7 | 8 | import Data.Kind (Constraint, Type) 9 | import GHC.TypeLits (Symbol) 10 | 11 | 12 | 13 | 14 | 15 | {- ONE -} 16 | 17 | -- | Let's look at the following type family to build a constraint: 18 | 19 | type family All (c :: Type -> Constraint) (xs :: [Type]) :: Constraint where 20 | All c '[] = () 21 | All c (x ': xs) = (c x, All c xs) 22 | 23 | -- | a. Why does it have to be restricted to 'Type'? Can you make this more 24 | -- general? 25 | 26 | -- | b. Why does it have to be restricted to 'Constraint'? Can you make this 27 | -- more general? Why is this harder? 28 | 29 | 30 | 31 | 32 | 33 | {- TWO -} 34 | 35 | -- | Sometimes, we're working with a lot of values, and we'd like some way of 36 | -- identifying them for the compiler. We could make newtypes, sure, but maybe 37 | -- there are loads, and we just don't want the boilerplate. Luckily for us, we 38 | -- can introduce this new type: 39 | 40 | data Tagged (name :: Symbol) (a :: Type) 41 | = Tagged { runTagged :: a } 42 | 43 | -- | 'Tagged' is just like 'Identity', except that it has a type-level string 44 | -- attached to it. This means we can write things like this: 45 | 46 | x :: Tagged "Important" Int 47 | x = Tagged 42 48 | 49 | y :: Tagged "Not so important" Int 50 | y = Tagged 23 51 | 52 | -- | What's more, we can use that tag in function signatures to restrict the 53 | -- input: 54 | 55 | f :: Tagged "Important" Int -> IO () 56 | f (Tagged x) = putStrLn (show x <> " is important!") 57 | 58 | -- | a. Symbols are all well and good, but wouldn't it be nicer if we could 59 | -- generalise this? 60 | 61 | -- | b. Can we generalise 'Type'? If so, how? If not, why not? 62 | 63 | -- | c. Often when we use the 'Tagged' type, we prefer a sum type (promoted 64 | -- with @DataKinds@) over strings. Why do you think this might be? 65 | 66 | 67 | 68 | 69 | 70 | {- THREE -} 71 | 72 | -- | We can use the following to test type-level equivalence. 73 | 74 | data a :=: b where 75 | Refl :: a :=: a 76 | 77 | -- | a. What do you think the kind of (:=:) is? 78 | 79 | -- | b. Does @PolyKinds@ make a difference to this kind? 80 | 81 | -- | c. Regardless of your answer to part (b), is this the most general kind we 82 | -- could possibly give this constructor? If not (hint: it's not), what more 83 | -- general kind could we give it, and how would we tell this to GHC? 84 | 85 | 86 | 87 | 88 | 89 | {- FOUR -} 90 | 91 | -- | We've talked about singleton types previously, and how they allow us to 92 | -- share a value between the value and type levels. Libraries such as 93 | -- @singletons@ provide many utilities for working with these types, many of 94 | -- which rely on a (type-level) function to get from a kind to its singleton. 95 | -- In our toy version of the library, that type family may look like this: 96 | 97 | type family Sing (x :: k) :: Type 98 | 99 | -- | Notice that it's an /open/ type family, thus we define instances for it 100 | -- using the @type instance Sing x = y@ syntax. 101 | 102 | -- | a. Here's the singleton for the @Bool@ kind. Remembering that the 103 | -- singleton for some @x@ of kind @Bool@ is @SBool x@, write a @Sing@ instance 104 | -- for the @Bool@ kind. Remember that we can pattern-match on /kinds/ in type 105 | -- families - if you're on the right lines, this is a one-liner! 106 | 107 | data SBool (b :: Bool) where 108 | STrue :: SBool 'True 109 | SFalse :: SBool 'False 110 | 111 | -- type instance Sing ... 112 | 113 | -- | b. Repeat the process for the @Nat@ kind. Again, if you're on the right 114 | -- lines, this is very nearly a copy-paste job! 115 | 116 | data Nat = Z | S Nat 117 | 118 | data SNat (n :: Nat) where 119 | SZ :: SNat 'Z 120 | SS :: SNat n -> SNat ('S n) 121 | 122 | 123 | 124 | 125 | 126 | {- FIVE -} 127 | 128 | -- | In dependently-typed programming, we talk about the notion of a "Sigma 129 | -- type", or "dependent pair". This is a single-constructor GADT that takes two 130 | -- arguments: a singleton for some kind, and some type indexed by the type 131 | -- represented by the singleton. Don't panic, let's do an example: 132 | 133 | -- | Consider the following GADT to describe a (fixed-length) list of strings: 134 | 135 | data Strings (n :: Nat) where 136 | SNil :: Strings 'Z 137 | (:>) :: String -> Strings n -> Strings ('S n) 138 | 139 | -- | If we have a working sigma type, we should be able to package a @Strings 140 | -- n@ and an @SNat n@ into @Sigma Strings@, existentialising the actual length. 141 | -- 142 | -- @ 143 | -- example :: [Sigma Strings] 144 | -- example 145 | -- = [ Sigma SZ SNil 146 | -- , Sigma (SS SZ) ("hi" :> SNil) 147 | -- , Sigma (SS (SS SZ)) ("hello" :> ("world" :> SNil)) 148 | -- ] 149 | -- @ 150 | 151 | -- | a. Write this type's definition: If you run the above example, the 152 | -- compiler should do a lot of the work for you... 153 | 154 | data Sigma (f :: Nat -> Type) where 155 | -- Sigma :: ... -> Sigma f 156 | 157 | -- | b. Surely, by now, you've guessed this question? Why are we restricting 158 | -- ourselves to 'Nat'? Don't we have some more general way to talk about 159 | -- singletons? The family of singletons? Any type within the family of 160 | -- singletons? Sing it with me! Generalise that type! 161 | 162 | -- | c. In exercise 5, we wrote a 'filter' function for 'Vector'. Could we 163 | -- rewrite this with a sigma type, perhaps? 164 | 165 | data Vector (a :: Type) (n :: Nat) where -- @n@ and @a@ flipped... Hmm, a clue! 166 | VNil :: Vector a 'Z 167 | VCons :: a -> Vector a n -> Vector a ('S n) 168 | 169 | 170 | 171 | 172 | 173 | 174 | {- SIX -} 175 | 176 | -- | Our sigma type is actually very useful. Let's imagine we're looking at 177 | -- a communication protocol over some network, and we label our packets as 178 | -- @Client@ or @Server@: 179 | 180 | data Label = Client | Server 181 | 182 | -- | Client data and server data are different, however: 183 | 184 | data ClientData 185 | = ClientData 186 | { name :: String 187 | , favouriteInt :: Int 188 | } 189 | 190 | data ServerData 191 | = ServerData 192 | { favouriteBool :: Bool 193 | , complimentaryUnit :: () 194 | } 195 | 196 | -- | a. Write a GADT indexed by the label that holds /either/ client data or 197 | -- server data. 198 | 199 | data Communication (label :: Label) where 200 | -- {{Fill this space with your academic excellence}} 201 | 202 | -- | b. Write a singleton for 'Label'. 203 | 204 | -- | c. Magically, we can now group together blocks of data with differing 205 | -- labels using @Sigma Communication@, and then pattern-match on the 'Sigma' 206 | -- constructor to find out which packet we have! Try it: 207 | 208 | -- serverLog :: [Sigma Communication] -> [ServerData] 209 | -- serverLog = error "YOU CAN DO IT" 210 | 211 | -- | d. Arguably, in this case, the Sigma type is overkill; what could we have 212 | -- done, perhaps using methods from previous chapters, to "hide" the label 213 | -- until we pattern-matched? 214 | -------------------------------------------------------------------------------- /08-PolyKinds/src/PolyKinds.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE PolyKinds #-} 3 | {-# LANGUAGE TypeFamilies #-} 4 | 5 | {- After a brief hiatus, we return with a -} module {- whose focus is on 6 | /kind polymorphism/, which is enabled by the -} PolyKinds {- extension. We'll 7 | find out -} where {- to use it, and wonder how we could do without it! -} 8 | 9 | {- 10 | Haskell, in general, /loves/ the notion of type polymorphism. Let's take a 11 | nice, familiar function: 12 | -} 13 | 14 | id :: a -> a 15 | id x = x 16 | 17 | {- 18 | This function is totally polymorphic in its @a@ variable: you can give it 19 | /any/ type of value, and you'll get a value of the same type back! Now, let's 20 | write a type family to do the same: 21 | -} 22 | 23 | type family Id (x :: a) :: a where 24 | Id x = x 25 | 26 | {- 27 | src/PolyKinds.hs:21:1-29: error: 28 | Unexpected kind variable ‘a’ Perhaps you intended to use PolyKinds 29 | In the declaration for type family ‘Id’ 30 | | 31 | 21 | type family Id (x :: a) :: a where 32 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 33 | 34 | GHC, being the marvel of modern engineering that it is, tells us exactly what 35 | to do here: we have a __kind variable__, which means we're trying to make our 36 | type family /polymorphic/ in its input and output types. In order to do that, 37 | we're going to need kind polymorphism, and hence @PolyKinds@. Let's look at 38 | another example: our humble 'Proxy' type. 39 | -} 40 | 41 | data Proxy a = Proxy 42 | 43 | {- 44 | If you type this into GHCi and then ask for the kind, you'll get nothing too 45 | surprising (remembering that @*@ is what GHC calls the @Type@ kind): 46 | 47 | >>> data Proxy a = Proxy 48 | >>> :k Proxy 49 | Proxy :: * -> * 50 | 51 | We see that GHC infers the kind of this type to be a type-constructor of one 52 | type argument, just like 'Maybe' or 'IO'. However, let's turn on @PolyKinds@: 53 | 54 | >>> :set -XPolyKinds 55 | >>> data Proxy a = Proxy 56 | >>> :k Proxy 57 | Proxy :: k -> * 58 | 59 | GHC has now looked into the constructors of the type and noticed that we 60 | don't use the type parameter as a type (unlike 'Maybe', for example, whose 61 | parameter is used in the 'Just' constructor). As a result, it has said that 62 | we can use /any/ kind for our argument - it is now kind-polymorphic! 63 | 64 | There's really not much more to this extension: just as we don't have to 65 | write this: 66 | -} 67 | 68 | idString :: String -> String 69 | idString x = x 70 | 71 | idInt :: Int -> Int 72 | idInt x = x 73 | 74 | idBool :: Bool -> Bool 75 | idBool x = x 76 | 77 | {- 78 | We now don't have to do this at the type level! The only thing worth 79 | mentioning, which won't bother us too much in these exercises, is that kind 80 | polymorphism has a trick that type polymorphism doesn't have: you can pattern 81 | match on types within kinds in a kind-polymorphic type family. Don't panic, 82 | we'll take a look: 83 | -} 84 | 85 | data Secrets 86 | 87 | type family Smuggler (x :: k) :: k where 88 | Smuggler (IO (Secrets, a)) = IO a 89 | Smuggler 0 = 1 90 | Smuggler a = a 91 | 92 | {- 93 | Huh. It seems that, while we said this function would work for /any/ kind, we 94 | actually can inspect the kind in our rules. As a result, type families aren't 95 | necessarily __parametric__: seeing `type family Id (x :: k) :: k` doesn't 96 | tell you as much about its implementation as `id :: a -> a` does for a value- 97 | level function: 98 | -} 99 | 100 | smuggle :: a -> a 101 | -- smuggle (xs :: IO (Secrets, a)) = fmap snd xs 102 | smuggle a = a 103 | 104 | {- 105 | Once you enable the extensions as GHC tells you, the error is that we 106 | couldn't match @IO (Secrets, a)@ with @a@. Because we said this function 107 | will work /for all/ a, we therefore can't know anything about the value! With 108 | type families, however, we can look at the kinds involved as more 109 | arguments to the function. The 'Smuggler' family has two inputs: the /type/ 110 | @x@, and the /kind/ @k@. 111 | 112 | This extra power with kind polymorphism makes for some exciting opportunities 113 | to abuse the type system, as we'll see in the exercises. 114 | -} 115 | -------------------------------------------------------------------------------- /09-MultiParamTypeClasses/exercise09.cabal: -------------------------------------------------------------------------------- 1 | name: exercise09 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | , containers 10 | hs-source-dirs: src 11 | exposed-modules: MultiParamTypeClasses 12 | , Exercises 13 | 14 | -------------------------------------------------------------------------------- /09-MultiParamTypeClasses/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ConstraintKinds #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE DeriveFunctor #-} 4 | {-# LANGUAGE DuplicateRecordFields #-} 5 | {-# LANGUAGE FlexibleContexts #-} 6 | {-# LANGUAGE FlexibleInstances #-} 7 | {-# LANGUAGE GADTs #-} 8 | {-# LANGUAGE KindSignatures #-} 9 | {-# LANGUAGE MultiParamTypeClasses #-} 10 | {-# LANGUAGE PolyKinds #-} 11 | {-# LANGUAGE TypeFamilies #-} 12 | {-# LANGUAGE TypeOperators #-} 13 | module Exercises where 14 | 15 | import Data.Kind (Constraint, Type) 16 | import Data.Map (Map) 17 | import Data.Proxy (Proxy (..)) 18 | 19 | 20 | 21 | 22 | 23 | {- ONE -} 24 | 25 | -- | Consider the following types: 26 | 27 | newtype MyInt = MyInt Int 28 | newtype YourInt = YourInt Int 29 | 30 | -- | As Haskell programmers, we love newtypes, so it would be super useful if 31 | -- we could define a class that relates a newtype to the type it wraps, while 32 | -- also giving us functions to get between them (we can call them 'wrap' and 33 | -- 'unwrap'). 34 | 35 | -- | a. Write the class! 36 | 37 | -- class Newtype ... ... where 38 | -- wrap :: ... 39 | -- unwrap :: ... 40 | 41 | -- | b. Write instances for 'MyInt' and 'YourInt'. 42 | 43 | -- | c. Write a function that adds together two values of the same type, 44 | -- providing that the type is a newtype around some type with a 'Num' instance. 45 | 46 | -- | d. We actually don't need @MultiParamTypeClasses@ for this if we use 47 | -- @TypeFamilies@. Look at the section on associated type instances here: 48 | -- https://wiki.haskell.org/GHC/Type_families#Associated_type_instances_2 - 49 | -- rewrite the class using an associated type, @Old@, to indicate the 50 | -- "unwrapped" type. What are the signatures of 'wrap' and 'unwrap'? 51 | 52 | 53 | 54 | 55 | 56 | {- TWO -} 57 | 58 | -- | Who says we have to limit ourselves to /types/ for our parameters? Let's 59 | -- look at the definition of 'traverse': 60 | 61 | traverse1 :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b) 62 | traverse1 = traverse 63 | 64 | -- | This is all very well, but we often don't need @f@ to be an 'Applicative'. 65 | -- For example, let's look at the good ol' 'Identity' type: 66 | 67 | newtype Identity a = Identity a 68 | deriving Functor -- LANGUAGE DeriveFunctor 69 | 70 | instance Foldable Identity where 71 | foldMap f (Identity x) = f x 72 | 73 | instance Traversable Identity where 74 | traverse f (Identity x) = Identity <$> f x 75 | 76 | -- | We can see that, in the @Traversable@ instance, we don't actually use 77 | -- @pure@ /or/ @(<*>)@ - we only use @<$>@! It would be nice if we could have a 78 | -- better @Traversable@ class that takes both the @t@ type /and/ the constraint 79 | -- we want on the @f@... 80 | 81 | -- | a. Write that little dazzler! What error do we get from GHC? What 82 | -- extension does it suggest to fix this? 83 | 84 | -- class Wanderable … … where 85 | -- wander :: … => (a -> f b) -> t a -> f (t b) 86 | 87 | -- | b. Write a 'Wanderable' instance for 'Identity'. 88 | 89 | -- | c. Write 'Wanderable' instances for 'Maybe', '[]', and 'Proxy', noting the 90 | -- differing constraints required on the @f@ type. '[]' might not work so well, 91 | -- and we'll look at /why/ in the next part of this question! 92 | 93 | -- | d. Assuming you turned on the extension suggested by GHC, why does the 94 | -- following produce an error? Using only the extensions we've seen so far, how 95 | -- could we solve this, perhaps in a way that involves another parameter to the 96 | -- 'wander' function? A parameter whose type could be annotated? (Don't worry - 97 | -- we'll see in later chapters that there are neater solutions to this 98 | -- problem!) 99 | 100 | -- test = wander Just [1, 2, 3] 101 | 102 | 103 | 104 | 105 | 106 | {- THREE -} 107 | 108 | data Nat = Z | S Nat 109 | 110 | data SNat (n :: Nat) where 111 | SZ :: SNat 'Z 112 | SS :: SNat n -> SNat ('S n) 113 | 114 | -- | In the @DataKinds@ chapter, we wrote the 'SmallerThan' data type, which 115 | -- we'll call 'Fin' from now on: 116 | 117 | data Fin (limit :: Nat) where 118 | FZ :: Fin ('S n) 119 | FS :: Fin n -> Fin ('S n) 120 | 121 | -- | We can write a class to take an 'SNat' to a 'Fin' using 122 | -- @MultiParamTypeClasses@. We can even use @TypeOperators@ to give our class a 123 | -- more intuitive name: 124 | 125 | class (x :: Nat) < (y :: Nat) where 126 | convert :: SNat x -> Fin y 127 | 128 | -- | a. Write the instance that says @Z@ is smaller than @S n@ for /any/ @n@. 129 | 130 | -- | b. Write an instance that says, if @x@ is smaller than @y@, then @S x@ is 131 | -- smaller than @S y@. 132 | 133 | -- | c. Write the inverse function for the class definition and its two 134 | -- instances. 135 | 136 | 137 | 138 | 139 | 140 | {- FOUR -} 141 | 142 | -- | In a couple places, we've seen the @(~)@ (or "equality") constraint being 143 | -- used. Essentially, we can think of it as a two-parameter typeclass with one 144 | -- instance. 145 | 146 | -- | a. Write that typeclass! 147 | 148 | -- | b. Write that instance! 149 | 150 | -- | c. When GHC sees @x ~ y@, it can apply anything it knows about @x@ to @y@, 151 | -- and vice versa. We don't have the same luxury with /our/ class, however – 152 | -- because we can't convince the compiler that only one instance will ever 153 | -- exist, it can't assume that we want the instance we've just written. No 154 | -- matter, though - we can just add two functions (@x -> y@ and @y -> x@) to 155 | -- our class to convert between the types. Write them, and don't overthink! 156 | 157 | -- | d. GHC can see @x ~ y@ and @y ~ z@, then deduce that @x ~ z@. Can we do 158 | -- the same? Perhaps with a second instance? Which pragma(s) do we need and 159 | -- why? Can we even solve this? 160 | 161 | 162 | 163 | 164 | 165 | {- FIVE -} 166 | 167 | -- | It wouldn't be a proper chapter without an @HList@, would it? 168 | 169 | data HList (xs :: [Type]) where 170 | -- In fact, you know what? You can definitely write an HList by now – I'll 171 | -- just put my feet up and wait here until you're done! 172 | 173 | -- | Consider the following class for taking the given number of elements from 174 | -- the front of an HList: 175 | 176 | class HTake (n :: Nat) (xs :: [Type]) (ys :: [Type]) where 177 | htake :: SNat n -> HList xs -> HList ys 178 | 179 | -- | a. Write an instance for taking 0 elements. 180 | 181 | -- | b. Write an instance for taking a non-zero number. You "may" need a 182 | -- constraint on this instance. 183 | 184 | -- | c. What case have we forgotten? How might we handle it? 185 | 186 | 187 | 188 | 189 | 190 | {- SIX -} 191 | 192 | -- | We could also imagine a type class to "pluck" types out of @HList@: 193 | 194 | class Pluck (x :: Type) (xs :: [Type]) where 195 | pluck :: HList xs -> x 196 | 197 | -- | a. Write an instance for when the head of @xs@ is equal to @x@. 198 | 199 | -- | b. Write an instance for when the head /isn't/ equal to @x@. 200 | 201 | -- | c. Using [the documentation for user-defined type 202 | -- errors](http://hackage.haskell.org/package/base-4.11.1.0/docs/GHC-TypeLits.html#g:4) 203 | -- as a guide, write a custom error message to show when you've recursed 204 | -- through the entire @xs@ list (or started with an empty @HList@) and haven't 205 | -- found the type you're trying to find. 206 | 207 | -- | d. Making any changes required for your particular HList syntax, why 208 | -- doesn't the following work? Hint: try running @:t 3@ in GHCi. 209 | 210 | -- mystery :: Int 211 | -- mystery = pluck (HCons 3 HNil) 212 | 213 | 214 | 215 | 216 | 217 | {- SEVEN -} 218 | 219 | -- | A variant is similar to an 'Either', but generalised to any non-zero 220 | -- number of parameters. Typically, we define it with two parameters: @Here@ 221 | -- and @There@. These tell us which "position" our value inhabits: 222 | 223 | -- variants :: [Variant '[Bool, Int, String]] 224 | -- variants = [ Here True, There (Here 3), There (There (Here "hello")) ] 225 | 226 | -- | a. Write the 'Variant' type to make the above example compile. 227 | 228 | data Variant (xs :: [Type]) where 229 | -- Here :: ... 230 | -- There :: ... 231 | 232 | -- | b. The example is /fine/, but there's a lot of 'Here'/'There' boilerplate. 233 | -- Wouldn't it be nice if we had a function that takes a type, and then returns 234 | -- you the value in the right position? Write it! If it works, the following 235 | -- should compile: @[inject True, inject (3 :: Int), inject "hello"]@. 236 | 237 | -- class Inject … … where 238 | -- inject :: … 239 | 240 | -- | c. Why did we have to annotate the 3? This is getting frustrating... do 241 | -- you have any (not necessarily good) ideas on how we /could/ solve it? 242 | 243 | 244 | 245 | 246 | 247 | {- EIGHT -} 248 | 249 | -- | As engineers, we are wont to over-think day-to-day problems in order to 250 | -- justify our existence to scrum masters. As such, we are compelled to visit 251 | -- our friendly neighbourhood angel investor with a new idea: given the weather 252 | -- and rough temperature, our web2.0, blockchain-ready app - chil.ly - will 253 | -- tell you whether or not you need a coat. Let's start by defining our inputs: 254 | 255 | data Weather = Sunny | Raining 256 | data Temperature = Hot | Cold 257 | 258 | -- ... and some singletons, why not? 259 | 260 | data SWeather (w :: Weather) where 261 | SSunny :: SWeather 'Sunny 262 | SRaining :: SWeather 'Raining 263 | 264 | data STemperature (t :: Temperature) where 265 | SHot :: STemperature 'Hot 266 | SCold :: STemperature 'Cold 267 | 268 | -- | Now, our app is going to be ready-for-scale, B2B, and proven with zero 269 | -- knowledge, so we want type safety /at the core/. Naturally, we've defined 270 | -- the relationship between the two domains as a type class. 271 | 272 | class Coat (a :: Weather) (b :: Temperature) where 273 | doINeedACoat :: SWeather a -> STemperature b -> Bool 274 | 275 | -- | It's early days, and we're just building an MVP, but there are some rules 276 | -- that /everyone/ knows, so they should be safe enough! 277 | 278 | -- No one needs a coat when it's sunny! 279 | instance Coat Sunny b where doINeedACoat _ _ = False 280 | 281 | -- It's freezing out there - put a coat on! 282 | instance Coat a Cold where doINeedACoat _ _ = True 283 | 284 | -- | Several months pass, and your app is used by billions of people around the 285 | -- world. All of a sudden, your engineers encounter a strange error: 286 | 287 | -- test :: Bool 288 | -- test = doINeedACoat SSunny SCold 289 | 290 | -- | Clearly, our data scientists never thought of a day that could 291 | -- simultaneously be sunny /and/ cold. After months of board meetings, a 292 | -- decision is made: you /should/ wear a coat on such a day. Thus, the 293 | -- __second__ rule is a higher priority. 294 | 295 | -- | a. Uncomment the above, and add OVERLAPPING and/or OVERLAPPABLE pragmas 296 | -- to prioritise the second rule. Why didn't that work? Which step of the 297 | -- instance resolution process is causing the failure? 298 | 299 | -- | b. Consulting the instance resolution steps, which pragma /could/ we use 300 | -- to solve this problem? Fix the problem accordingly. 301 | 302 | -- | c. In spite of its scary name, can we verify that our use of it /is/ 303 | -- undeserving of the first two letters of its name? 304 | 305 | 306 | 307 | 308 | 309 | {- NINE -} 310 | 311 | -- | The 'Show' typeclass has two instances with which we're probably quite 312 | -- familiar: 313 | 314 | -- instance Show a => Show [a] 315 | -- instance Show String 316 | 317 | -- | a. Are these in conflict? When? 318 | 319 | -- | b. Let's say we want to define an instance for any @f a@ where the @f@ is 320 | -- 'Foldable', by converting our type to a list and then showing that. Is there 321 | -- a pragma we can add to the first 'Show' instance above so as to preserve 322 | -- current behaviour? Would we need /more/ pragmas than this? 323 | 324 | -- | c. Somewhat confusingly, we've now introduced incoherence: depending on 325 | -- whether or not I've imported this module, 'show' will behave in different 326 | -- ways. Your colleague suggests that your use of pragmas is the root issue 327 | -- here, but they are missing the bigger issue; what have we done? How could we 328 | -- have avoided it? 329 | 330 | 331 | 332 | 333 | 334 | {- TEN -} 335 | 336 | -- | Let's imagine we have some types in our codebase: 337 | 338 | newtype UserId = UserId Int 339 | 340 | data User 341 | = User 342 | { id :: UserId 343 | , knownAs :: String 344 | } 345 | 346 | newtype CommentId = CommentId Int 347 | 348 | data Comment 349 | = Comment 350 | { id :: CommentId 351 | , author :: UserId 352 | , text :: String 353 | } 354 | 355 | data Status = Blocked | Deleted 356 | 357 | -- | In order to better facilitate mobile devices, we now want to introduce 358 | -- caching. I start work, and eventually slide a pull request into your DMs: 359 | 360 | class UserCache where 361 | storeUser :: User -> Map UserId User -> Map UserId User 362 | loadUser :: Map UserId User -> UserId -> Either Status User 363 | 364 | class CommentCache where 365 | storeComment :: Comment -> Map CommentId Comment -> Map CommentId Comment 366 | loadComment :: Map CommentId Comment -> CommentId -> Maybe Comment 367 | 368 | -- | "This is silly", you exclaim. "These classes only differ in three ways! We 369 | -- could write this as a multi-parameter type class!" 370 | 371 | -- | a. What are those three ways? Could we turn them into parameters to a 372 | -- typeclass? Do it! 373 | 374 | -- | b. Write instances for 'User' and 'Comment', and feel free to implement 375 | -- them as 'undefined' or 'error'. Now, before uncommenting the following, can 376 | -- you see what will go wrong? (If you don't see an error, try to call it in 377 | -- GHCi...) 378 | 379 | -- oops cache = load cache (UserId (123 :: Int)) 380 | 381 | -- | c. Do we know of a sneaky trick that would allow us to fix this? Possibly 382 | -- involving constraints? Try! 383 | -------------------------------------------------------------------------------- /09-MultiParamTypeClasses/src/MultiParamTypeClasses.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE KindSignatures #-} 4 | {-# LANGUAGE MultiParamTypeClasses #-} 5 | 6 | {- Our -} module {- du jour is strangely-abbreviated and weirdly-capitalised: 7 | the -} MultiParamTypeClasses {- extension opens a lot of doors for us that 8 | we'll explore in this chapter /and/ future chapters - Let's see -} where {- we 9 | end up! -} 10 | 11 | import Data.Kind (Type) 12 | 13 | {- 14 | By now in your Haskell quest, you'll have seen many type classes. Already, 15 | the `README` of this project has mentioned two such classes: `Monoid` and 16 | `Monad`. Add these to `Eq`, `Ord`, `Show`, `Functor`... well, we're using a 17 | lot of typeclasses! 18 | 19 | The common thread between these is that the class defines a characteristic of 20 | a **single** type. 'Eq' tells us that /a type/ has a notion of equivalence. 21 | 'Show' tells us that /a type/ has a string representation. 22 | 23 | Let's think about the rough idea behind the 'Show' class: 24 | -} 25 | 26 | class SimpleShow (x :: Type) where 27 | simpleShow :: x -> String 28 | 29 | {- 30 | Our class represents a type that can be transformed into a string. What if we 31 | want to generalise the "target" type, though? What if we want to define a 32 | class that characterises this relationship? Enter @MultiParamTypeClasses@. 33 | -} 34 | 35 | class Transform (source :: Type) (target :: Type) where 36 | transform :: source -> target 37 | 38 | {- 39 | We've said here that the 'Transform' class defines a relationship between two 40 | types for which we can implement the 'transform' function. This function has 41 | a type that is hopefully not too surprising: @Transform s t => s -> t@. Now, 42 | how do we write instances? 43 | -} 44 | 45 | instance Transform Bool Int where 46 | transform x = if x then 1 else 0 47 | 48 | {- 49 | Perfect! We write it just as we would a normal instance, except there are 50 | more types in the instance head. We can even use type parameters in our 51 | instance heads (note that using 'String' here means we need 52 | 'FlexibleInstances' - the same rules apply, regardless of how many parameters 53 | you have): 54 | -} 55 | 56 | instance Show x => Transform x String where 57 | transform = show 58 | 59 | {- 60 | The @MultiParamTypeClasses@ extension also allows us to have /nullary/ 61 | (0-argument) classes, though we won't give them much time as they're 62 | relatively straightforward. PureScript has a type class called 63 | [Partial](https://github.com/purescript/documentation/blob/master/guides/The-Partial-type-class.md) 64 | to keep track of partial functions, which gives a good intuition of the ways 65 | in which these can be useful. 66 | -} 67 | 68 | {- 69 | Let's talk briefly about what happens when we have conflicting definitions. 70 | This isn't specific to @MultiParamTypeClasses@, and all the following rules 71 | apply to single-parameter type classes as well, though we're certainly more 72 | likely to run into these use cases in multi-parameter situations. Let's 73 | imagine, for example, that we want a special string representation of the 74 | unit type, '()'. 75 | -} 76 | 77 | instance Transform () String where 78 | transform _ = "UNIT" 79 | 80 | {- 81 | All good, but we get some ugly errors when we try to use it: 82 | -} 83 | 84 | -- test :: String 85 | -- test = transform () 86 | 87 | {- 88 | • Overlapping instances for Transform () String 89 | arising from a use of ‘transform’ 90 | Matching instances: 91 | instance Show x => Transform x String 92 | -- Defined at src/MultiParamTypeClasses.hs:55:10 93 | instance Transform () String 94 | -- Defined at src/MultiParamTypeClasses.hs:73:10 95 | 96 | GHC has correctly realised that we actually have __two__ instances that match 97 | our needs. Because of this ambiguity, it gives us an error - it simply 98 | refuses to guess. How, then, would we solve this ambiguity? Luckily, GHC 99 | provides some language pragmas to help here - let's write a new instance: 100 | -} 101 | 102 | instance {-# OVERLAPPING #-} Transform String String where 103 | transform = id 104 | 105 | test2 :: String 106 | test2 = transform "hello" 107 | 108 | {- 109 | @OVERLAPPING@ is how we tell GHC that, given the choice between this instance 110 | and another, this is the "preferred" option, and should be picked. The 111 | opposite pragma is @OVERLAPPABLE@, which says "pick the other one". 112 | Fortunately, this is an either/or deal - when we have a choice between two 113 | instances, GHC needs to find an @OVERLAPPING@ /or/ and @OVERLAPPABLE@ 114 | instance to make a decision. Both are fine as long as they agree, though! 115 | 116 | There's a third, more mystical pragma, however: @OVERLAPS@. Let's have a look 117 | at such an instance now: 118 | -} 119 | 120 | data Which = A | B | C deriving Show 121 | 122 | instance {-# OVERLAPS #-} Transform (f a) Which where 123 | transform _ = A 124 | 125 | {- 126 | Now, let's introduce a couple of others: 127 | -} 128 | 129 | instance Transform a Which where 130 | transform _ = B 131 | 132 | instance Transform (Maybe a) Which where 133 | transform _ = C 134 | 135 | {- 136 | ... and let's use them! 137 | -} 138 | 139 | test3 :: Which 140 | test3 = transform (Just (42 :: Int)) -- Prints C 141 | 142 | test4 :: Which 143 | test4 = transform [True] -- Prints A 144 | 145 | {- 146 | Interesting... so, in the first case, we have two matching instances - the 147 | @A@ instance and the @C@ instance. @Maybe a@ is deemed more "specific" than 148 | @f a@, and so @OVERLAPS@ acts like @OVERLAPPABLE@, and we get the @C@ 149 | instance! 150 | 151 | In the second case, however, our @[Bool]@ matches both the @A@ instance and 152 | the @B@ instance. This time, @f a@ is more "specific" than @a@, so @OVERLAPS@ 153 | acts like @OVERLAPPING@! 154 | 155 | @OVERLAPS@ is a strange pragma, and you won't see it used too much in the 156 | wild. Furthermore, "specific" is a strange thing to intuit, but we can think 157 | about it in GHC's terms: if @x@ is a valid substitution of @y@, but @y@ isn't 158 | a valid substitution for @x@, then @x@ is more "specific". For example, 159 | @Maybe Int@ would be a valid substitution for @Maybe a@, but not vice versa, 160 | so we consider @Maybe Int@ to be __more specific__. I hope that makes 161 | sense... 162 | -} 163 | 164 | {- 165 | There is one last oft-misunderstood pragma to discuss: INCOHERENT. To 166 | understand this, we'll paraphrase [a GHC user guide 167 | entry](https://downloads.haskell.org/~ghc/7.10.1/docs/html/users_guide/type-class-extensions.html#instance-overlap) 168 | and step through GHC's process when picking an instance. 169 | 170 | 1. Find all instances that match given what we know already* (those into 171 | which we could "substitute" our type). 172 | 173 | 2. Discard any less-specific overlappable instances (or those not /as/ 174 | specific as an overlapping instance). 175 | 176 | 3. Ignoring any instances marked @INCOHERENT@, if exactly one match remains, 177 | we have a winner! If all remaining instances /are/ marked @INCOHERENT@, 178 | pick one of them at random, and we have a winner! (Not really random, but 179 | the choice isn't done according to some defined behaviour). 180 | 181 | 4. If our winner is incoherent, we're done! 182 | 183 | 5. If not, find all the instances that /could/ match if we knew more*. If all 184 | these other instances are incoherent, return our winner! Otherwise, we've 185 | failed, and we don't know enough to decide :( 186 | 187 | * Knowing enough is important here. Let's clean the `Transform` slate and 188 | look at this: 189 | -} 190 | 191 | class Sneaky (source :: Type) (target :: Type) where 192 | familiar :: source -> target 193 | 194 | instance Sneaky Float Float where 195 | familiar = id 196 | 197 | {- 198 | Now, let's use `familiar` in a situation without giving GHC a way to figure 199 | out the second type: 200 | -} 201 | 202 | -- -- UNCOMMENT ME 203 | -- huh = familiar (3.0 :: Float) 204 | 205 | {- 206 | As we're starting to learn, GHC is actually pretty good at telling us what is 207 | upsetting it. 208 | 209 | • Ambiguous type variable ‘target0’ arising from a use of ‘familiar’ 210 | prevents the constraint ‘(Sneaky Float target0)’ from being solved. 211 | 212 | GHC is telling us that, until it knows what the _second_ type is, it can't 213 | figure out which instance to pick! Why? Well, for a start, we've just seen 214 | @instance Transform a Which@, which might well be what we're after! Until GHC 215 | knows more, it can't sensibly choose one or the other. Of course, there are a 216 | few tricks to play here. 217 | -} 218 | 219 | instance a ~ [Double] => Sneaky Double a where 220 | familiar = pure 221 | 222 | heh = familiar (3.0 :: Double) 223 | 224 | {- 225 | This time, no complaints! Why? Because in the instance head (the bit on the 226 | right-hand side of the @=>@ arrow), we've said we have an instance for 227 | @Double@ and /any/ second parameter you like. After we've successfully chosen 228 | that instance, the constraints /then/ tell us that the second type /must/ be 229 | @Double@, and we'll get an error if it isn't. This separation between 230 | "instance resolution" and "constraint solving" is one worth exploring, as 231 | many of our clever tricks rely on it heavily. 232 | 233 | Now, some of our types might have interesting instances for 'Sneaky', but 234 | we'd like to offer 'Sneaky a a' as a "default" instance. In other words, we 235 | only want to use this instance __if all others fail__. Sound familiar? 236 | -} 237 | 238 | -- -- UNCOMMENT ME 239 | -- instance {-# INCOHERENT #-} a ~ b => Sneaky a b where 240 | -- familiar = id 241 | 242 | {- 243 | Here's a scary-looking line of code, right? What we're saying here is, if you 244 | can't find an instance to suit your needs, this "fallback" instance will 245 | work. The only constraint is that your @a@ and @b@ types must be the same 246 | type. Because it's marked as @INCOHERENT@, we'll totally ignore it /unless/ 247 | we have no other option. Perfect! 248 | 249 | Now, this all sounds sensible, so... why the scary name? Friends, we have 250 | some misconceptions to address: 251 | 252 | 1. _Most_ "incoherence" around type classes comes from what we call /orphan 253 | instances/. These are instances defined for types that exist neither in 254 | the same module as the type declaration /nor/ the class declaration. If 255 | we avoid orphan instances (by, for example, creating @newtype@s when we 256 | want different behaviours instead of overlapping existing instances), 257 | there is no incoherence with @OVERLAPPING@, @OVERLAPS@, or @OVERLAPPABLE@, 258 | thus they are simply safe – and extremely useful – tools for us to use. 259 | 260 | 2. @INCOHERENT@ instances only become incoherent when GHC is given a choice 261 | between more than one of them. If you only ever have one defined - or find 262 | a clever way to avoid ever matching more than one* - then this is a very 263 | scary name for a not-very-scary idea. Just... be careful with this one, 264 | OK? If you can convince yourself that, following the steps of instance 265 | resolution above, GHC's selection will always be predictable, then this is 266 | all good. /Be careful/. 267 | 268 | * https://kcsongor.github.io/generic-deriving-bifunctor/#incoherent-instances 269 | -} 270 | -------------------------------------------------------------------------------- /10-FunctionalDependencies/exercise10.cabal: -------------------------------------------------------------------------------- 1 | name: exercise10 2 | version: 0.1.0.0 3 | build-type: Simple 4 | cabal-version: >= 1.10 5 | 6 | library 7 | default-language: Haskell2010 8 | build-depends: base 9 | hs-source-dirs: src 10 | exposed-modules: FunctionalDependencies 11 | , Exercises 12 | 13 | -------------------------------------------------------------------------------- /10-FunctionalDependencies/src/Exercises.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE DeriveGeneric #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE FlexibleInstances #-} 5 | {-# LANGUAGE FunctionalDependencies #-} 6 | {-# LANGUAGE GADTs #-} 7 | {-# LANGUAGE PolyKinds #-} 8 | {-# LANGUAGE TypeFamilies #-} 9 | {-# LANGUAGE TypeOperators #-} 10 | {-# LANGUAGE UndecidableInstances #-} 11 | module Exercises where 12 | 13 | import Data.Kind (Type) 14 | import GHC.TypeLits (Symbol) 15 | import GHC.Generics (Generic (..)) 16 | import qualified GHC.Generics as G 17 | 18 | 19 | 20 | 21 | 22 | {- ONE -} 23 | 24 | -- | Recall an old friend, the 'Newtype' class: 25 | 26 | class Newtype (new :: Type) (old :: Type) where 27 | wrap :: old -> new 28 | unwrap :: new -> old 29 | 30 | -- | a. Can we add a functional dependency to this class? 31 | 32 | -- | b. Why can't we add two? 33 | 34 | 35 | 36 | 37 | 38 | {- TWO -} 39 | 40 | -- | Let's go back to a problem we had in the last exercise, and imagine a very 41 | -- simple cache in IO. Uncomment the following: 42 | 43 | -- class CanCache (entity :: Type) (index :: Type) where 44 | -- store :: entity -> IO () 45 | -- load :: index -> IO (Maybe entity) 46 | 47 | -- | a. Uh oh - there's already a problem! Any @entity@ type should have a 48 | -- fixed type of id/@index@, though... if only we could convince GHC... Could 49 | -- you have a go? 50 | 51 | -- | b. @IO@ is fine, but it would be nice if we could choose the functor when 52 | -- we call @store@ or @load@... can we parameterise it in some way? 53 | 54 | -- | c. Is there any sort of functional dependency that relates our 55 | -- parameterised functor to @entity@ or @index@? If so, how? If not, why not? 56 | 57 | 58 | 59 | 60 | 61 | {- THREE -} 62 | 63 | -- | Let's re-introduce one of our old favourites: 64 | data Nat = Z | S Nat 65 | 66 | -- | When we did our chapter on @TypeFamilies@, we wrote an @Add@ family to add 67 | -- two type-level naturals together. If we do a side-by-side comparison of the 68 | -- equivalent "class-based" approach: 69 | 70 | class Add (x :: Nat) (y :: Nat) (z :: Nat) | x y -> z 71 | type family Add' (x :: Nat) (y :: Nat) :: Nat 72 | 73 | -- | We see here that there are parallels between classes and type families. 74 | -- Type families produce a result, not a constraint, though we could write 75 | -- @Add' x y ~ z => ...@ to mean the same thing as @Add x y z => ...@. Also, 76 | -- the result of a type family is determined by its inputs - something we can 77 | -- express as a functional dependency! 78 | 79 | -- | a. Write the two required instances for the 'Add' class by 80 | -- pattern-matching on the first argument. Remember that instances can have 81 | -- constraints, and this is how we do recursion! 82 | 83 | -- | b. By our analogy, a type family has only "one functional dependency" - 84 | -- all its inputs to its one output. Can we write _more_ functional 85 | -- dependencies for @Add@? Aside from @x y -> z@? 86 | 87 | -- | c. We know with addition, @x + y = z@ implies @y + x = z@ and @z - x = y@. 88 | -- This should mean that any pair of these three variables should determine the 89 | -- other! Why couldn't we write all the possible functional dependencies that 90 | -- /should/ make sense? 91 | 92 | 93 | 94 | 95 | {- FOUR -} 96 | 97 | data Proxy (a :: k) = Proxy 98 | 99 | -- | As we all know, type signatures are /not/ documentation. This is really 100 | -- because the names of types are far too confusing. To that end, we can give 101 | -- our types friendlier names to make the coding experience less intimidating: 102 | 103 | class (x :: k) `IsNamed` (label :: Symbol) where 104 | fromName :: Proxy x -> Proxy label 105 | fromName _ = Proxy 106 | 107 | toName :: Proxy label -> Proxy x 108 | toName _ = Proxy 109 | 110 | -- | Now we have this class, we can get to work! 111 | 112 | instance Int `IsNamed` "Dylan" 113 | instance IO `IsNamed` "Barbara" 114 | instance Float `IsNamed` "Kenneth" 115 | 116 | -- | a. In our glorious new utopia, we decide to enact a law that says, "No two 117 | -- types shall have the same name". Similarly, "No type shall have two names". 118 | -- Is there a way to get GHC to help us uphold the law? 119 | 120 | -- | b. Write the identity function restricted to types named "Kenneth". 121 | 122 | -- | c. Can you think of a less-contrived reason why labelling certain types 123 | -- might be useful in real-world code? 124 | 125 | 126 | 127 | 128 | 129 | {- FIVE -} 130 | 131 | -- | Here's a fun little class: 132 | class Omnipresent (r :: Symbol) 133 | 134 | -- | Here's a fun little instance: 135 | instance Omnipresent "Tom!" 136 | 137 | -- | a. Is there a way to enforce that no other instance of this class can ever 138 | -- exist? Do we /need/ variables on the left-hand side of a functional 139 | -- dependency arrow? 140 | 141 | -- | b. Can you think of a time you would ever want this guarantee? Is this 142 | -- "trick" something you can think of a practical reason for doing? Perhaps if 143 | -- we added a method to the class? (Very much an open question). 144 | 145 | -- | c. Add another similarly-omnipresent parameter to this type class. 146 | 147 | 148 | 149 | 150 | 151 | {- SIX -} 152 | 153 | -- | You knew it was coming, didn't you? 154 | 155 | data HList (xs :: [Type]) where 156 | HNil :: HList '[] 157 | HCons :: x -> HList xs -> HList (x ': xs) 158 | 159 | data SNat (n :: Nat) where 160 | SZ :: SNat 'Z 161 | SS :: SNat n -> SNat ('S n) 162 | 163 | -- | a. Write a function (probably in a class) that takes an 'SNat' and an 164 | -- 'HList', and returns the value at the 'SNat''s index within the 'HList'. 165 | 166 | -- | b. Add the appropriate functional dependency. 167 | 168 | -- | c. Write a custom type error! 169 | 170 | -- | d. Implement 'take' for the 'HList'. 171 | 172 | 173 | 174 | 175 | 176 | {- SEVEN -} 177 | 178 | -- | Recall our variant type: 179 | 180 | data Variant (xs :: [Type]) where 181 | Here :: x -> Variant (x ': xs) 182 | There :: Variant xs -> Variant (y ': xs) 183 | 184 | -- | We previously wrote a function to "inject" a value into a variant: 185 | 186 | class Inject (x :: Type) (xs :: [Type]) where 187 | inject :: x -> Variant xs 188 | 189 | instance Inject x (x ': xs) where 190 | inject = Here 191 | 192 | instance {-# OVERLAPPING #-} Inject x xs 193 | => Inject x (y ': xs) where 194 | inject = There . inject 195 | 196 | -- | Write a function to "project" a value /out of/ a variant. In other words, 197 | -- I would like a function that takes a proxy of a type, a variant containing 198 | -- that type, and returns /either/ a value of that type /or/ the variant 199 | -- /excluding/ that type: 200 | -- 201 | -- @ 202 | -- project (Proxy :: Proxy Bool) (inject True :: Variant '[Int, String, Bool]) 203 | -- === Left Bool :: Either Bool (Variant '[Int, String]) 204 | -- @ 205 | 206 | 207 | 208 | 209 | 210 | {- EIGHT -} 211 | 212 | -- | It would be nice if I could update a particular index of an HList by 213 | -- providing an index and a (possibly-type-changing) function. For example: 214 | -- 215 | -- @ 216 | -- update (SS SZ) length (HCons True (HCons "Hello" HNil)) 217 | -- === HCons True (HCons 5 HNil) 218 | -- @ 219 | 220 | -- | Write the type class required to implement this function, along with all 221 | -- its instances and functional dependencies. 222 | 223 | 224 | 225 | 226 | 227 | {- NINE -} 228 | 229 | -- | If you've made it this far, you're more than capable of digesting and 230 | -- understanding some advanced GHC docs! Read the documentation at 231 | -- http://hackage.haskell.org/package/base-4.12.0.0/docs/GHC-Generics.html, and 232 | -- keep going until you hit 'Generic1' - we won't worry about that today. 233 | 234 | -- | We can write a little function to get the name of a type as a type-level 235 | -- symbol like so: 236 | 237 | class NameOf (x :: Type) (name :: Symbol) | x -> name 238 | instance GNameOf (Rep x) name => NameOf x name 239 | 240 | -- | We then have to implement this class that examines the generic tree... 241 | class GNameOf (rep :: Type -> Type) (name :: Symbol) | rep -> name 242 | instance GNameOf (G.D1 ('G.MetaData name a b c) d) name 243 | 244 | -- | Write a function to get the names of the constructors of a type as a 245 | -- type-level list of symbols. 246 | 247 | 248 | 249 | 250 | 251 | {- TEN -} 252 | 253 | -- | In the standard library, we have a series of @liftA*@ functions, such as 254 | -- 'liftA2', 'liftA3', 'liftA4'... wouldn't it be nice if we just had /one/ 255 | -- function called 'lift' that generalised all these? 256 | -- 257 | -- liftA1 :: Applicative f => (a -> b) -> f a -> f b 258 | -- liftA1 = lift 259 | -- 260 | -- liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c 261 | -- liftA2 = lift 262 | -- 263 | -- 264 | -- liftA3 :: Applicative f => (a -> b -> c -> d) -> f a -> f b -> f c -> f d 265 | -- liftA3 = lift 266 | 267 | -- | Write this function, essentially generalising the f <$> a <*> b <*> c... 268 | -- pattern. It may help to see it as pure f <*> a <*> b <*> c..., and start 269 | -- with a function like this: 270 | 271 | -- lift :: (Applicative f, Lift f i o) => i -> o 272 | -- lift = lift' . pure 273 | 274 | -- | @class Lift f i o ... where lift' :: ...@ is your job! If you get this 275 | -- right, perhaps with some careful use of @INCOHERENT@, equality constraints, 276 | -- and functional dependencies, you should be able to get some pretty amazing 277 | -- type inference: 278 | -- 279 | -- >>> :t lift (++) 280 | -- lift (++) :: Applicative f => f [a] -> f [a] -> f [a] 281 | -------------------------------------------------------------------------------- /10-FunctionalDependencies/src/FunctionalDependencies.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE GADTs #-} 4 | {-# LANGUAGE KindSignatures #-} 5 | {-# LANGUAGE FunctionalDependencies #-} 6 | 7 | {- I'd quite like a -} module {- please, 8 | on -} FunctionalDependencies {- 9 | On the left I'll write types 10 | to determine the right, 11 | and infer them -} where{-ever I please. -} 12 | 13 | {- 14 | In the last session, we looked at multi-parameter type classes as a method of 15 | defining relationships between types. This was great, but we ran into a few 16 | tricky problems. 17 | 18 | Let's start with this friendly little typeclass: 19 | -} 20 | 21 | class Transform a b where 22 | transform :: a -> b 23 | 24 | {- 25 | Everything looks fine, so let's write an instance! 26 | -} 27 | 28 | instance Transform Int String where 29 | transform = show 30 | 31 | {- 32 | All good, now let's try to use it... 33 | -} 34 | 35 | -- test :: IO () 36 | -- test = print (transform (3 :: Int)) 37 | 38 | {- 39 | • Ambiguous type variable ‘a0’ arising from a use of ‘print’ 40 | prevents the constraint ‘(Show a0)’ from being solved. 41 | Probable fix: use a type annotation to specify what ‘a0’ should be. 42 | 43 | Hm. This isn't ideal. Let's break down what happened: 44 | 45 | - @transform (3 :: Int)@ has type @forall b. Transform Int b => b@ 46 | - 'print' has type @forall a. Show a => a -> IO ()@ 47 | 48 | GHC, it seems, has no idea what should come out of the @transform@ call, and 49 | no strong opinions on what should go /in/ the 'print' call - it could be 50 | anything 'Show'able! 51 | 52 | Not to worry, though: we've dealt with this before! 53 | -} 54 | 55 | instance b ~ String => Transform Bool b where 56 | transform = show 57 | 58 | tested :: IO () 59 | tested = print (transform True) 60 | 61 | {- 62 | Phew! GHC sees @transform :: forall b. Transform Bool b => b@, and then 63 | notices an instance head that exactly says @Transform Bool b@! "Huzzah", it 64 | exclaims, and deduces that the output type be 'String'. 65 | 66 | Let's make a little upgrade to our typeclass: 67 | -} 68 | 69 | -- class HelpfulTransformer a b where 70 | -- f :: a -> b 71 | -- help :: a 72 | 73 | {- 74 | We now have both a transformation function, /and/ a get-out-of-jail-free 75 | function to grab us an @a@ if we forgot to bring one. It's contrived, sure, 76 | but it illustrates something we'll see a /lot/ in the exercises. 77 | 78 | Uncomment this, and you'll get a hefty complaint from GHC: 79 | 80 | src/FunctionalDependencies.hs:75:3-11: error: 81 | • Could not deduce (HelpfulTransformer a b0) 82 | from the context: HelpfulTransformer a b 83 | bound by the type signature for: 84 | help :: forall a b. HelpfulTransformer a b => a 85 | at src/FunctionalDependencies.hs:75:3-11 86 | The type variable ‘b0’ is ambiguous 87 | • In the ambiguity check for ‘help’ 88 | To defer the ambiguity check to use sites, enable AllowAmbiguousTypes 89 | 90 | Hmm. If we read this a couple times, it actually starts to make sense: our 91 | @help@ function doesn't mention @b@, so we can't tell which pair of @a@ and 92 | @b@ we should choose in order to find an appropriate instance. GHC words this 93 | as, "@b@ is an /ambiguous type/". 94 | 95 | Now, we could turn on @AllowAmbiguousTypes@, but we've already seen that this 96 | probably isn't a particularly helpful extension given only what we've seen so 97 | far. It certainly doesn't get us any closer to being able to /use/ the @help@ 98 | function! What we /need/ is a way of saying: 99 | 100 | /Each @a@ has a unique @b@ value. If you know @a@, there will be /at most 101 | one/ instance of the class in scope, and that will tell you what @b@ is./ 102 | 103 | This is just like regular functions: each possible input has a single output. 104 | It doesn't have to be a /unique/ output, but knowing the input to a function 105 | is enough to calculate the output. If this is getting too abstract, let's 106 | code some more: 107 | -} 108 | 109 | -- The functional dependency syntax --v 110 | class MoreHelpfulTransformer a b | a -> b where 111 | moreF :: a -> b 112 | moreHelp :: a 113 | 114 | instance MoreHelpfulTransformer Int String where 115 | moreF = show 116 | moreHelp = 25 117 | 118 | instance MoreHelpfulTransformer String String where 119 | moreF = id 120 | moreHelp = "Tom" 121 | 122 | {- 123 | No complaints! GHC is now happy in the knowledge that it won't find two 124 | instances with the same left-hand type, and so it can /infer/ the right-hand 125 | type for you. As well as helping our @help@ function, this also helps with 126 | our earlier problem, too! 127 | -} 128 | 129 | testest :: IO () 130 | testest = print (moreF (25 :: Int)) 131 | 132 | {- 133 | No fancy equality constraint tricks here! GHC sees that call to @moreF@, sees 134 | that @a ~ Int@, and uses the functional dependency to deduce that @b ~ 135 | String!@ it really is quite magical. 136 | 137 | As a general starter intuition, we can think of a functional dependency like 138 | @a -> b@ as saying, "you'll never find two instances where the @a@s are the 139 | same and the @b@s aren't". 140 | 141 | Of course, you might be wondering: what happens if the left-hand variable 142 | /isn't/ unique? What happens if I write this: 143 | -} 144 | 145 | -- instance MoreHelpfulTransformer String Bool where 146 | -- moreF = (== "Tom") 147 | -- moreHelp = True 148 | 149 | {- 150 | src/FunctionalDependencies.hs:120:10-45: error: 151 | Functional dependencies conflict between instance declarations: 152 | instance MoreHelpfulTransformer String String 153 | -- Defined at src/FunctionalDependencies.hs:120:10 154 | instance MoreHelpfulTransformer String Bool 155 | -- Defined at src/FunctionalDependencies.hs:128:10 156 | 157 | The compiler's onto your tricks! The functional dependency tells GHC that it 158 | can both /infer/ the variables on the right-hand side of the functional 159 | dependency, and that it can /enforce/ uniqueness on the left-hand side. 160 | 161 | In the exercises, we'll see that you can actually put any number of variables 162 | on the left-hand side of a functional dependency arrow. You can also put 163 | _more than one_ on the right-hand side. You can even write several functional 164 | dependencies! 165 | -} 166 | 167 | class Example a b c d 168 | | a b c -> d -- Many left-hand things - every @a@/@b@/@c@ combination must 169 | -- have the same @d@! 170 | 171 | , a d -> b c -- Many right-hand things - every @a@/@d@ combination must have 172 | -- the same @b@/@c@! 173 | 174 | , c -> a -- Commas separate multiple functional dependencies. 175 | 176 | {- 177 | The only thing that is important is that there cannot be any 178 | overlap between combinations of the left-hand variables in your instances. If 179 | you have @class C a b c d | a b -> c d@, your instances must all have unique 180 | pairs of @a@ and @b@ types! 181 | -} 182 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tom Harding 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GHC exercises 🚀 2 | 3 | _For solutions, please see the `Exercises.hs` modules inside [the `answers` 4 | branch](https://github.com/i-am-tom/haskell-exercises/tree/answers)._ 5 | 6 | OK, you know your **monoids** from your **monads**. You know how to write a 7 | terminal application or two. What's next? What are these language extensions 8 | that keep being mentioned? How "type-safe" can you _really_ be? 9 | 10 | This repository, I hope, will provide some **stepping stones**. We'll go 11 | through extensions one at a time, and build up a richer vocabulary for talking 12 | about Haskell programs, and look to move our assertions up into types, where 13 | they can be verified at compile time. 14 | 15 | --- 16 | 17 | ## What this _isn't_. 18 | 19 | This is a deep dive into GHC extensions, the power that each one gives us, and 20 | how we can combine extensions to achieve very strong guarantees at 21 | compile-time. This is _not_ based around concepts; there won't be sections on 22 | "dependently-typed programming", or "generic programming", though these 23 | concepts will turn up throughout as we dig deeper into the extensions. 24 | 25 | > If you're interested in something more project-based, I absolutely, 1000% 26 | > recommend [Thinking with Types](https://leanpub.com/thinking-with-types), 27 | > written by [Sandy Maguire](https://github.com/isovector). It is a 28 | > **fantastic** resource, and one on which I already rely when explaining 29 | > concepts to others. 30 | 31 | ## Contents 32 | 33 | 1. `GADTs` 34 | 2. `FlexibleInstances` 35 | 3. `KindSignatures` 36 | 4. `DataKinds` 37 | 5. `RankNTypes` 38 | 6. `TypeFamilies` 39 | 7. `ConstraintKinds` 40 | 8. `PolyKinds` 41 | 9. `MultiParamTypeClasses` 42 | 10. `FunctionalDependencies` 43 | 44 | ## Setup 45 | 46 | Assuming you have [Cabal](https://www.haskell.org/cabal/) or 47 | [Stack](https://docs.haskellstack.org/en/stable/README/) setup, you should be 48 | able to navigate to any of the `exercise*` directories, and run your usual 49 | commands: 50 | 51 | ### Repl 52 | 53 | ``` 54 | $ stack repl 55 | $ cabal repl 56 | ``` 57 | 58 | ### Build 59 | 60 | ``` 61 | $ stack build 62 | $ cabal build 63 | ``` 64 | 65 | It's going to make it a lot easier to iterate through the exercises if you 66 | `cabal install ghcid` or `stack install ghcid`. Just as above, once this is 67 | done, you can navigate to the exercise directory and run it with your preferred 68 | repl command: 69 | 70 | ``` 71 | $ ghcid -c "stack repl" 72 | $ ghcid -c "cabal repl" 73 | ``` 74 | --------------------------------------------------------------------------------