Learn You a Haskell for Great Good! - Miran Lipovaca [67]
Because that’s the same as the following:
(b -> c) -> (Either a) b -> (Either a) c
The function is mapped in the case of a Right value constructor, but it isn’t mapped in the case of a Left. Why is that? Well, looking back at how the Either a b type is defined, we see this:
data Either a b = Left a | Right b
If we wanted to map one function over both of them, a and b would need to be the same type. Think about it: If we try to map a function that takes a string and returns a string, and b is a string but a is a number, it won’t really work out. Also, considering what fmap’s type would be if it operated only on Either a b values, we can see that the first parameter must remain the same, while the second one can change, and the first parameter is actualized by the Left value constructor.
This also goes nicely with our box analogy if we think of the Left part as sort of an empty box with an error message written on the side telling us why it’s empty.
Maps from Data.Map can also be made into functor values, because they hold values (or not!). In the case of Map k v, fmap will map a function v -> v' over a map of type Map k v and return a map of type Map k v'.
Note
The ' character has no special meaning in types, just as it has no special meaning when naming values. It’s just used to denote things that are similar, but slightly changed.
As an exercise, you can try to figure out how Map k is made an instance of Functor by yourself!
As you’ve seen from the examples, with Functor, type classes can represent pretty cool higher-order concepts. You’ve also had some more practice with partially applying types and making instances. In Chapter 11, we’ll take a look at some laws that apply for functors.
Kinds and Some Type-Foo
Type constructors take other types as parameters to eventually produce concrete types. This behavior is similar to that of functions, which take values as parameters to produce values. Also like functions, type constructors can be partially applied. For example, Either String is a type constructor that takes one type and produces a concrete type, like Either String Int.
In this section, we’ll take a look at formally defining how types are applied to type constructors. You don’t really need to read this section to continue on your magical Haskell quest, but it may help you to see how Haskell’s type system works. And if you don’t quite understand everything right now, that’s okay, too.
Values like 3, "YEAH", or takeWhile (functions are also values—we can pass them around and such) each has their own types. Types are little labels that values carry so that we can reason about the values. But types have their own little labels called kinds. A kind is more or less the type of a type. This may sound a bit weird and confusing, but it’s actually a really cool concept.
What are kinds, and what are they good for? Well, let’s examine the kind of a type by using the :k command in GHCi:
ghci> :k Int
Int :: *
What does that * mean? It indicates that the type is a concrete type. A concrete type is a type that doesn’t take any type parameters. Values can have only types that are concrete types. If I had to read * out loud (I haven’t had to do that yet), I would say “star,” or just “type.”
Okay, now let’s see what the kind of Maybe is:
ghci> :k Maybe
Maybe :: * -> *
This kind tells us that the Maybe type constructor takes one concrete type (like Int) and returns a concrete type (like Maybe Int). Just as Int -> Int means that a function takes an Int and returns an Int, * -> * means that the type constructor takes one concrete type and returns a concrete type. Let’s apply the type parameter to Maybe and see what the kind of that type is:
ghci> :k Maybe Int
Maybe Int :: *
Just as you might have expected, we applied the type parameter to Maybe and got back a concrete type (that’s what * -> * means). A parallel (although not equivalent—types and kinds are two different things) to this is if we call :t isUpper and :t isUpper 'A'. The isUpper function has a type of Char -> Bool, and isUpper