diff options
Diffstat (limited to 'lec09/notes.md')
| -rw-r--r-- | lec09/notes.md | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/lec09/notes.md b/lec09/notes.md new file mode 100644 index 0000000..7025392 --- /dev/null +++ b/lec09/notes.md @@ -0,0 +1,78 @@ +# Lecture 9: Functors + +## Motivation + +In short, since we've seen both the standard `map` over lists and other ad-hoc `map`s for things like `Tree`s so far, we'd like to be able to design a `map` function that can work with any type. + +If we wanted to design a `map` that abstracts the *container* that it maps over, we'd end up with the following function signature. + + thingMap :: (a -> b) -> f a -> f b + +## A brief digression on kinds + +We know that every expression has a type. Analogously, types themselves also have "types" called **kinds**. It's important to note that in Haskell kinds do not have a further "type" classification, they're the "last level". + +In GHCi, whenever we want to know the kind of a type, we can ask via the `:kind` command (or `:k` for short). + + Prelude> :k Int + Int :: * + +`Int` has kind `*`, but so do `Bool`, `Char`, and even `Maybe Int`. A type with kind `*` can hold values. It checks out: `Int` can have values like `1`, `2`, etc.; `Bool` has `True` and `False`; `Maybe Int`, similarly to `Int`, has `Nothing`, `Just 1`, `Just 2`, etc. + +What's the kind of, for example, `Maybe` alone? `Maybe` cannot hold values, unlike `Maybe Int`, `Maybe Bool`, and similar. When we ask GHCi, it says: + + Prelude> :k Maybe + Maybe :: * -> * + +`Maybe` is a *function on types*, more commonly known as **type constructor**: when it receives a type of kind `*` (like `Int`), it produces another type of kind `*` (like `Maybe Int`). Other similar type constructors with kind `* -> *` are `Tree` and the list type constructor `[]`. Note that in Haskell `[Int]` is just a special syntax for `[] Int`. + + Prelude> :k [] + [] :: * -> * + Prelude :k [] Int + [] Int :: * + Prelude> :k [Int] + [Int] :: * + +Let's look at the kind of the function type constructor `(->)`. It takes two type arguments and, since it's an operator, we can use it in the infix style. + + Prelude> :k (->) + (->) :: * -> * -> * + Prelude> :k (->) Int Char + (->) Int Char :: * + Prelude> :k Int -> Char + Int -> Char :: * + +Here's a more challenging example: + + data Funny f a = Funny a (f a) + Prelude> :k Funny + Funny :: (* -> *) -> * -> * + +GHCi, through *kind inference*, can tell us that the `Funny` type takes two arguments, one of kind `* -> *` and another of kind `*`, in order to construct the final type. We call types like `Funny` **higher-order type constructors**, analogously to higher-order functions. Just like higher-order functions, higher-order type constructors can be partially applied as well. + +## Functor + +The mapping pattern is a powerful one. We saw it applied to both traditional lists and custom data types, like `JoinList`s or `Tree`s. It would be really useful to have an even more generalized version of `map`, but can we make it? As it turns out, we cannot. The reason is that, since we don't know anything about the underlying type that we want to map over, we cannot use its values in any way. + +Let's see why. Consider the function type signature introduced at the beginning of this lecture. Now we know that `f` is a type of kind `* -> *`. + + thingMap :: (a -> b) -> f a -> f b + +Can we define a function with this signature? Regardless of how hard we try, nothing works for all types. The types in this signature are just too generic and we cannot suppose anything for them. + +The solution to this is a type class called `Functor`, defined in the Prelude. + + class Functor f where + fmap :: (a -> b) -> f a -> f b + +This enables us to implement a specific map for each of our own custom types, as long as the type in question has kind `* -> *`, and use `fmap` to homogeneously map a function over values of any type, as long as there is an instance for it. The most obvious type is the list type `[]`, but we could come up with an implementation for `Maybe` + + instance Functor Maybe where + fmap _ Nothing = Nothing + fmap h (Just a) = Just (h a) + +or even for `IO`, which results in an `IO` action which first runs the value of type `IO a`, and then applies the function to transform the result, using the bind operator. + + instance Functor IO where + fmap f ioa = ioa >>= (\a -> return (f a)) + -- or alternatively: ioa >>= (return . f) |