Learn You a Haskell for Great Good! - Miran Lipovaca [68]
We used :k on a type to get its kind, in the same way as we can use :t on a value to get its type. Again, types are the labels of values, and kinds are the labels of types, and there are parallels between the two.
Now let’s look at the kind of Either:
ghci> :k Either
Either :: * -> * -> *
This tells us that Either takes two concrete types as type parameters to produce a concrete type. It also looks somewhat like the type declaration of a function that takes two values and returns something. Type constructors are curried (just like functions), so we can partially apply them, as you can see here:
ghci> :k Either String
Either String :: * -> *
ghci> :k Either String Int
Either String Int :: *
When we wanted to make Either a part of the Functor type class, we needed to partially apply it, because Functor wants types that take only one parameter, while Either takes two. In other words, Functor wants types of kind * -> *, so we needed to partially apply Either to get this instead of its original kind, * -> * -> *.
Looking at the definition of Functor again, we can see that the f type variable is used as a type that takes one concrete type to produce a concrete type:
class Functor f where
fmap :: (a -> b) -> f a -> f b
We know it must produce a concrete type, because it’s used as the type of a value in a function. And from that, we can deduce that types that want to be friends with Functor must be of kind * -> *.
Chapter 8. Input and Output
In this chapter, you’re going to learn how to receive input from the keyboard and print stuff to the screen.
But first, we’ll cover the basics of input and output (I/O):
What are I/O actions?
How do I/O actions enable us to do I/O?
When are I/O actions actually performed?
Dealing with I/O brings up the issue of constraints on how Haskell functions can work, so we’ll look at how we get around that first.
Separating the Pure from the Impure
By now, you’re used to the fact that Haskell is a purely functional language. Instead of giving the computer a series of steps to execute, you give it definitions of what certain things are. In addition, a function isn’t allowed to have side effects. A function can give us back only some result based on the parameters we supplied to it. If a function is called two times with the same parameters, it must return the same result.
While this may seem a bit limiting at first, it’s actually really cool. In an imperative language, you have no guarantee that a simple function that should just crunch some numbers won’t burn down your house or kidnap your dog while crunching those numbers. For instance, when we were making a binary search tree in the previous chapter, we didn’t insert an element into a tree by modifying the tree itself; instead, our function actually returned a new tree with the new element inserted into that.
The fact that functions cannot change state—like updating global variables, for example—is good, because it helps us reason about our programs. However, there’s one problem with this: If a function can’t change anything in the world, how is it supposed to tell us what it calculated? To do that, it must change the state of an output device (usually the state of the screen), which then emits photons that travel to our brain, which changes the state of our mind, man.
But don’t despair, all is not lost. Haskell has a really clever system for dealing with functions that have side effects. It neatly separates the part of our program that is pure and the part of our program that is impure, which does all the dirty work like talking to the keyboard and the screen. With those two parts separated, we can still reason about our pure program and take advantage of all the things that purity offers—like laziness, robustness, and composability—while easily communicating with the outside world. You’ll see this at work in this chapter.
Hello, World!
Until now, we’ve always loaded our functions into GHCi