Happy Learn Haskell Tutorial Vol 1

Buy now at Leanpub

Please Buy now at Leanpub

Written and illustrated by GetContented.

Published on 2016-05-20.


Contents

Main Table of Contents
Previous chapter: 3. Types as Jigsaw Pieces
4. The Main Road
... 4.1. How a Haskell Program is Made
... 4.2. Purity
... 4.3. Everything in Haskell is pure
... 4.4. An Analogy
... 4.5. Haskell is Awesome
Next chapter: 5. Function Magic

4. The Main Road 🔗

We discovered that main is the entry-point of all Haskell programs. In other words, the point where they start executing. We also discovered that main is an action, so now we’re going to delve a little bit into actions, IO, and what purity means.

Bear with us while we work through this part. Haskell works differently than you would think, and it’s important to get a clear mental picture of how it behaves.

4.1. How a Haskell Program is Made 🔗

Haskell programs are made by writing definitions in text files, and running a program called a compiler on those text files. The compiler understands Haskell, and translates the text files as Haskell into an executable program, which we can then run.

4.2. Purity 🔗

In Haskell, all the things you can write are called pure: definitions, values, expressions and functions, even the values that produce actions.

Pure here means they are consistent, equational and express truths about value. That makes them easy to think and reason about. They’re expressed in the world of the computer, where pure thought can exist. All the expressions in Haskell use this purity, which makes writing software joyful because we can lock parts of our programs down into separated, predictable behaviour that almost always works as we think it will.

To understand this, let’s think about the way addition works. It never, ever changes. If you add the numbers 1 and 5, you will never get a different result than 6. Of course! This is what we mean by pure — we can talk about what is true or false in this “world” with some certainty. Contrast this to something from the real world: the current time. This is not a pure value because it is always changing.

Let's see a program that does this. Don't worry if you don't understand anything about it yet. We'll explain it in later chapters:


-- a program to
-- add 1 and 5

main :: IO ()
main = print (1 + 5)

Every time you run the program above, it will return 6. It can never change, which is because it only uses simple pure functions and pure values to obtain its result. Contrast that to the following program, which uses pure IO actions that contain non-pure values:


-- a program to get and print
-- the current time as seconds

import Data.Time.Clock.POSIX

main :: IO ()
main = getPOSIXTime >>= print

When we run this program, it takes the current time from IO, and passes it to some screen-printing code which is built into the IO portion of the print function, which prints it out on the screen. Every time you run this program you'll get a different answer, because the time is not a pure value. It's always changing.

Something to note, though, is that getPOSIXTime and print are still pure functions. They will always return the same thing: a description of non-pure actions. That is, they describe non-pure values and functionalities. That means when we write our program we can still use pure functions like the above, but when it is run, those values can send non-pure values and functions around, like the time or printing things to the screen. These are called IO actions, because they describe some action on the input/output context.

4.3. Everything in Haskell is pure 🔗

Some more about this. So how can we say everything in Haskell is pure, if we just showed you a Haskell program that deals with the time which we just explained is an impure value? Well, the Haskell program is pure, and only expresses pure values and functions, but the IO type allows us to describe and contain computations and values that deal with the non-pure real world!

Pure values and expressions and functions are much easier to reason about because they're simple and direct, which is why it’s nice to program in Haskell: It lets us talk about the non-pure world using pure descriptions.

Unfortunately, most of the interesting things we want to program are not completely pure. Haskell has certain special functions and values that are marked as IO actions. You’ve seen one or two already. These are also pure, like everything in Haskell, but they give us a gateway into the messier “real world”. They let us describe actions that can happen in the real world.

In Haskell, the types of things that let us do this are made with a constructor named IO, which stands for Input/Output. That is, the world of the error-prone, non-pure, complicated, real world where things are always changing, and where there is input and output from keyboards, mice, disks, time, the internet, random numbers and printers. The real world is usually hard to think about simply, and much more difficult to program for. It doesn’t obey the same rules as the pure world and is harder to lock down into predictable behaviour because it’s so complex and complicated.


getPOSIXTime :: IO POSIXTime

As we can see, the type of the function that can get the POSIX time (a type of time value) is an IO type, which shows that it is an action in the impure world. That means it will return a POSIXTime when it is executed.

So, IO actions can be executed, which is what happens when their IO behaviour is activated - this is how programs are run. This IO behaviour is effectively invisible to the pure world. The pure world can never “see outside” to the real IO world, however the IO world can use pure values, functions and expressions without problem. Running a program is how we execute IO actions. When we compose our IO actions with other IO actions and pure functions, we can build up useful, functioning programs.

4.4. An Analogy 🔗

Let’s use an analogy here: think about a simple electric circuit: a battery, some wires, a switch and a light globe. By themselves, all of these things are simple, “pure” things. The light and switch are slightly different than the others, but they are still just themselves. However, the switch can “do input” into the circuit, and the light globe can “do output” from the circuit in the form of glowing light. The wire and battery can do no such things. They don’t have any action in the real world at all.

If we connect these components into a circuit by binding them together, we can throw the switch (that is, give the circuit some input) and the light bulb will cause there to be light (that is, we’ll get some output). This doesn’t change what these components are, obviously. However, some of the parts provide effects in the world outside the “world” of their pure value inside the circuit. The wires are like our totally pure non-IO functions in Haskell because they don’t do anything outside the circuit: they’re not like the light or the switch.

You can see how all of these components are in themselves purely what they are, and all of them can be used as part of other bigger composed circuits, which in total have some IO action on the real world, but also that some of these component (such as the wires) don’t actually do any actions in the world of IO for the circuit, but are necessary for the whole circuit.

In a similar way, IO actions have a foot in both worlds: while being able to be evaluated like ordinary Haskell expressions, which does nothing in the IO world, they also contain the ability to be executed: that is, to interact with the outside world and effect it in some way, and so they are marked with the IO type marker. To create programs that interact with the IO world, we bind combinations of these IO actions together along with pure functions to create bigger IO actions that can do what we want.

4.5. Haskell is Awesome 🔗

Haskell is an awesome language: it’s capable of letting us cleanly think about and build pure expressions and functions, and also lets us connect these pure things up to the real, messy world in a way that keeps as much of our programs simple, clear and separate as possible.

When a program is composed of pieces like this, it’s very easy to reuse parts of it, and it’s much easier to spot problems and adjust things.

Note that Haskell programmers will often refer to a pure IO value as an action, as well as referring to the part of one that effects the real world, such as the action of putting a message on the screen. This can sometimes be confusing, so just remember it can mean either.


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: 3. Types as Jigsaw Pieces
4. The Main Road
... 4.1. How a Haskell Program is Made
... 4.2. Purity
... 4.3. Everything in Haskell is pure
... 4.4. An Analogy
... 4.5. Haskell is Awesome
Next chapter: 5. Function Magic