Happy Learn Haskell Tutorial Vol 1

Buy now at Leanpub

Please Buy now at Leanpub

Written and illustrated by GetContented.

Published on 2017-07-08.


Contents

Main Table of Contents
Previous chapter: 2. Your First Step
3. Types as Jigsaw Pieces
... 3.1. A String Value
... 3.2. Puzzles
... 3.3. Definitions again
... 3.4. Type Annotations
... 3.5. Types for Functions
... 3.6. IO Actions as Puzzles
... 3.7. Putting values together like puzzle pieces
... 3.8. The whole program
... 3.9. Another way to look at it
... 3.10. What is a Function?
... 3.11. Arguments?
... 3.12. Nonsense Programs?
... 3.13. The shape of main
... 3.14. The two simple programs
... 3.15. Pulling the definitions apart more
... 3.16. Are signatures mandatory?
... 3.17. Signatures as documentation
... 3.18. Homework
Next chapter: 4. The Main Road

3. Types as Jigsaw Pieces 🔗

Ok so we previously looked at reading our first program and began to understand it. Let’s look at some more programs and expressions. This time, though, we’ll focus on the types of the elements to explain what’s going on.

3.1. A String Value 🔗

Here is a String value:


"Dolly wants a cracker"

A nice way to think about values is as if they’re magical puzzle pieces that can shrink and grow as needed to fit together. If values are puzzle pieces in this analogy, then their types would be the shapes of the puzzle pieces. This analogy only really works for simple types, but it can be handy.

3.2. Puzzles 🔗

So, perhaps we might think of that String value like this:

3.3. Definitions again 🔗

Let’s look at a definition for this String value. We make definitions so we can re-use things in other parts of our program later on. We’ll name this one m (as in message):


m = "Dolly wants a cracker"

As we’ve seen briefly before, this is telling Haskell that we want to define the name m as being the String that is literally "Dolly wants a cracker". Writing definitions for names like this is just like writing a mini dictionary for Haskell, so it can find out what we mean when we use these names in expressions in other parts of our programs.

3.4. Type Annotations 🔗

Now we’ll see what it looks like when we include the code to describe the type of m, too.


m :: String
m = "Dolly wants a cracker"

Writing the type annotation like this helps both us and Haskell to know what types values and expressions are. This is also called a type signature. You probably worked out that (::) specifies the type of a name in the same way that (=) is used to specify the meaning of a name.

Haskell now knows that m means "Dolly wants a cracker" whenever we use it in another expression, and that it is a String. Anywhere we use the m variable, Haskell will use our String value; they now mean the same thing.

3.5. Types for Functions 🔗

Now, what about putStrLn? As we know, this is a name that Haskell already has ready-made for us. Let’s look at its type:


putStrLn :: String -> IO ()

(Note: You will never need to write this type in your own code because it's already defined. We’ve just shown it here so you can see what it is, and how to work with it.)

We can read this as “putStrLn has type String to IO action”, or “putStrLn is a function from String value to IO action”. String is the function’s input type, (->) signifies that it's a function, and goes between the input and output types, and IO () is the function’s output type.

3.6. IO Actions as Puzzles 🔗

Let’s look at what an IO () value might look like if we imagined it as a puzzle piece (the shape is completely made up, but we'll use it to explain the types of values):

What about putStrLn? Well, it’d need be a value of type IO () once it had a String popped into it. We could imagine that it looked like this, roughly:

It’s not an IO () value. It’s something that can be, when supplied with a String value. See how that makes it a kind of mapping between values of these two types?

3.7. Putting values together like puzzle pieces 🔗

When we put a String value in that pink gap, we get an expression that will evaluate to a value whose type is IO ().


putStrLn "Dolly wants a cracker" :: IO ()

As you can see, we can put a type signature on almost any expression. For example, here’s another expression that means the same thing, but with too much annotation:


putStrLn ("Dolly wants a cracker" :: String) :: IO ()

We put a sub-expression type annotation for the String as well as an annotation for the whole expression! Very silly. In real code, we’d never see an expression like this, because Haskell can almost always work out the types from the context, which is called type inference. Notice we’re using parentheses around the string so Haskell knows which signature goes with which expression.

Of course we could use our m that we defined earlier instead, and show off its type annotation, too:


putStrLn m :: IO ()

3.8. The whole program 🔗

So this expression is an IO () value, and because main needs to be an IO () as well, we can see that one possible definition of main could be this putStrLn expression. Here’s the whole program to print out the String:


m :: String
m = "Dolly wants a cracker"

main :: IO ()
main = putStrLn m

3.9. Another way to look at it 🔗

So far so good, now let’s see some similar things visualised slightly differently:

When we put a value next to a function, like in the expression putStrLn "No!", we can say putStrLn is taking the literal String whose value is "No!" as its argument. The word argument means the parameter to a function. In Haskell, wrting two things with a space between usually means that the thing on the left is a function and it will be applied to the value or expression on the right.

3.10. What is a Function? 🔗

A function is a value that expresses a particular relationship between the values of types. That is, it relates one set of values to another set of values. The putStrLn function relates String values to particular kinds of IO actions, for example (ones that will output the input String values). If you plug (or connect) a String value into putStrLn as we have seen above, together they form an expression of type IO action.

Almost always when we write Haskell programs, we try to make sure our functions are complete. This means that we’ve written them in such a way that they work with every possible value for their input types.

3.11. Arguments? 🔗

The “hole” position that putStrLn has is called an argument (or parameter). By giving a value to a function as an argument as described, we are telling Haskell to connect them into one expression that is able to be evaluated, into a value of its return type.

In the picture above, main is shown as being square-shaped. By itself, putStrLn doesn’t have the same shape as main, so it needs something “plugged” into it before we can equate it to main.

Once the String is plugged in, the whole thing is an expression with the same shape that is required for main, which means we can write a program with the definition for main we saw above.


main :: IO ()
main = putStrLn "No!"

Another way to say this is that the IO action that results from connecting these together is a value. Before it has the String connected to it, it's an IO () value that is a function of its String argument (and that function is named putStrLn). You can see how this means that functions are a mapping from any value of their input type to a particular value of their output type.

3.12. Nonsense Programs? 🔗

It’s important to remember that Haskell receives programs as text files, so there is nothing stopping you from writing programs that make no sense to it, such as trying to provide something other than a String as an argument to putStrLn.


main :: IO ()
main = putStrLn 573 -- this will not compile!

Haskell won’t let you compile or run obviously incorrect programs, though. It will give you an error if you try. You should try to compile the incorrect program above. Haskell will tell you there is a type-checking error.

3.13. The shape of main 🔗

So, we saw that main needs its definition to be of a certain “shape”. Haskell requires that main is an IO action. Its type is written IO (), which is an IO action that returns nothing of interest (but does some action when executed). We call this “Nothing of Interest” value Unit, and it’s written like this: (). That is both its type, and how we write its value. Later you’ll see that this is called an empty tuple, which is just another name for an emptly wrapper that can have nothing in it. We use the double-colon operator (::) to mark the type that a definition, expression or value is “of”. Below, we’ll see this in operation some more.

3.14. The two simple programs 🔗

Let’s take a look at these two programs which were referenced in the drawings earlier:


main :: IO ()
main = putStrLn "Yay"

main :: IO ()
main = putStrLn "No!"

By the way, you can only define an identifier once in each Haskell file, so don’t try to put both the above definitions in one file and expect it to compile. Haskell will give you an error. An identifier is just another name for a variable or term, as discussed in an earlier chapter. However Haskell won’t let you confuse it by naming two different things the same identifier, so trying to is an error.

The first program prints out “Yay” on the screen, and the second one prints “No!”

As discussed we read the (::) operator as “has type”. IO () is the type of IO actions that wrap the empty tuple. The empty tuple is a container that can never have anything inside of it, so it’s used as a simple way to express the value of having no value at all.

Handily for us, IO () is the same type of value that putStrLn returns when we give it a String as an argument.

3.15. Pulling the definitions apart more 🔗

Let’s read another program that does exactly the same thing as the No! program above, but goes about it a little differently:


message :: String
message = "No!"

main :: IO ()
main = putStrLn message

We’ve seen this before: we just pulled the No! String out into its own definition (naming it with the identifier message), with its own type signature. Don’t let it scare you, it’s pretty simple. It just means message is a String, and its value is "No!".

3.16. Are signatures mandatory? 🔗

Do you have to write type signatures? Not always! We started the book with a definition for main that had no type signature, and then added one, so you can leave them off — as long as Haskell can infer what you mean by itself. Don't worry, it'll tell you if it can't work it out.

Haskell uses a pretty nifty feature called type inference to figure out what types things should be if you don’t write type signatures for them.

3.17. Signatures as documentation 🔗

However, it’s often a good idea to put type signatures in so you and others know what your code means later. We recommend doing this because it improves the readability of your program, too.

Did you notice you can use type signatures to let you work out what to plug in to what? They can be incredibly useful when programming.

3.18. Homework 🔗

Your homework is to do an internet search on Haskell programs and see if you can identify at least ten definitions, and ten type signatures. Don’t get too worried by odd looking things, just stick to the homework. We want to get you familiar with what these things look like.


If you’ve enjoyed reading this, please consider purchasing a copy at Leanpub today

Please follow us, and check out our videos Follow @HappyLearnTutes

Also, Volume 2 is now in beta and being written! Show your support and register your interest at its Leanpub site.


Main Table of Contents
Previous chapter: 2. Your First Step
3. Types as Jigsaw Pieces
... 3.1. A String Value
... 3.2. Puzzles
... 3.3. Definitions again
... 3.4. Type Annotations
... 3.5. Types for Functions
... 3.6. IO Actions as Puzzles
... 3.7. Putting values together like puzzle pieces
... 3.8. The whole program
... 3.9. Another way to look at it
... 3.10. What is a Function?
... 3.11. Arguments?
... 3.12. Nonsense Programs?
... 3.13. The shape of main
... 3.14. The two simple programs
... 3.15. Pulling the definitions apart more
... 3.16. Are signatures mandatory?
... 3.17. Signatures as documentation
... 3.18. Homework
Next chapter: 4. The Main Road