Compose :: Melbourne 2016 Haskell Workshop

An workshop intended to introduce Haskell to newcomers to functional-programming.

Running on Day-Two of Compose :: Melbourne.

If you wish to attend on the day, then please register via Eventbrite.

Press "o" to toggle only showing the most important content
Press "t" to toggle showing the table of contents
Workshop

Outcomes include...

  • Creating, editing, running and interacting with Haskell programs
  • Building the confidence to solve problems in the wild with Haskell
  • Developing an understanding of the Haskell ecosystem
  • Interacting with others in a collaborative environment

If you are attending the workshop, make sure that you RSVP via Eventbrite. Please also attempt to have the required items from the 'Resources' section available for your use during the workshop.

If you would like to volunteer to help assist at the workshop, please send an email to the .


Table of Contents

Resources Resources Available and Required 1m
Welcome Motivation, Overview, and Approach 15m
Setup Setting up your Haskell environment 15m
Ecosystem Resources and Community 30m
Introduction Introductory Exercises 30m
Types The Haskell Type System 30m
ADTs Modelling with data in Haskell 1h
Type-Classes Polymorphism, FP style 30m
Monads IO Monad, Do-Notation 1h
Guessing-Game Let's Make a Guessing Game 1h

Required Resources

Before you begin you will require the following...

A Text-Editor

We are assuming previous programming experience, however, if you would like a recommendation, have a look at Atom, Visual Studio Code, Emacs or Vim. Just make sure that you are fluent enough to embark on exercises as they appear in the workshop.

Stack

In order to run the programs written during this workshop you will need a Haskell installation. The easiest way to get up and running is to install Stack.

A Copy of the Workshop Github Project

The exercises in the project are available in runnable form in the workshop source.

You can grab the source from GitHub:

git clone https://github.com/composeconference/compose_haskell_workshop.git

Other Useful Resources

These resources are available to help you with any issues you face when learning Haskell:

#haskell on Freenode

An IRC channel dedicated to discussion of Haskell. This is often the easiest place to fire off a one-off question that is simple enough not to warrant a permanent listing on the internet.

Hackage

Hackage is the primary repository for Haskell packages. It is public, searchable, versioned, and uses Cabal package metadata for classification. Furthermore, this can be used to easily browse package contents, documentation and source-code.

For example, browse the Shake package and look at some of the Modules.

Hoogle

Hoogle is a Haskell module and function search-engine. Hoogle allows you to take advantage of the granular type-system used by Haskell to search not just for function-names, but for function type-signatures.

For example, have a look for the function with signature Text -> ByteString.

/r/haskell

For Reddit users, /r/haskell is a very useful resource with a great deal of information regarding recent developments in the Haskell ecosystem and community. This is a good place to ask some more advanced questions or start a flame-war.

Learn You a Haskell (For Great Good)

Learn You a Haskell (For Great Good) is a wonderful introductory text on Haskell.

Haskell Programming from First Principles

The latest and greatest comprehensive text for learning Haskell.


Welcome

Welcome to the Compose :: Melbourne 2016 Haskell Workshop.

Running on Day-Two of Compose :: Melbourne.

This intent of this workshop is to provide a working introduction to Haskell for programmers in Melbourne who have not yet used the language in anger.

The workshop is split into chapters. The chapters will start with a few trivial introductory exercises to get your fingers warmed up, then the meat - exercises that should be able to be solved using the material introduced up to that point (with help and hints available if needed).

At the beginning of each chapter will be a small listing of terms, so that you know what you can expect to encounter throughout the text of the chapter. This isn't intended to function as a glossary, but simply give you a heads-up on the scope and let you know what's coming up!

Each chapter will conclude with an open-question. This question should provide inspiration for further consideration of how harder problems could be solved using Haskell, and for more advanced attendees, can be attacked instead of twiddling your thumbs after finishing the main exercise.


Setup

This section will help you get up and running so that you can participate in the workshop and complete the exercises.

Lexicon

----------- ------------- ------------
Stack setup GHCi
Calculations $PATH Loading
:reload Redefine GHC
Compile Optimisation Install
Pointfree Ecosystem

Ensure that you have the following programs installed and functioning correctly:

Stack

Check that you have stack installed:

stack --version

This should output something similar to:

Version 1.1.2 x86_64 hpack-0.14.0

Otherwise, install it!

curl -sSL https://get.haskellstack.org/ | sh
stack setup
stack ghci
> 1 + 1

This should output:

2
You can use GHCi to perform calculations other than just "1 + 1".

Here is an example session:

[Prelude] > 1 + 2 + 3
6
[Prelude] > 100 / 2
50.0
[Prelude] > 6 ^ 7
279936
[Prelude] > ^D
Leaving GHCi.
Using GHCi...

Calculate the price of 42-bakers-dozens of eggs at $3 per-egg.
-- Note that a baker's dozen is 13!
[Prelude] 42 * 13 * 3
1638
If ghci is on your PATH, then you can invoke it directly,
however, if you have just installed stack, then you will
need to invoke ghci indirectly by calling

> stack exec -- ghci [ARGS]

Loading files in GHCi

There are many ways to load and execute Haskell code. For the purposes of this workshop, if you do not already have a workflow you are comfortable with, then we suggest the following steps:

  • Write and edit your programs in files ending in the extension ".hs"
  • When you are ready to test your program, load it into GHCi
  • After making modifications to your program, reload the program in GHCi

Say you had written the following program test.hs:

main = print "hello world"

Load the file in GHCi to see it in action:

> stack exec -- ghci test.hs
GHCi, version 7.6.2: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
[*Main] > main
"hello world"

... Unfortunately there is a bug in the program, so in your editor you make the change required to print "hello, world" with the mandated comma:

main = print "hello, world"

Now, back in GHCi, you can reload the program without exiting the REPL (Read Eval Print Loop):

[*Main] > :reload
[1 of 1] Compiling Main             ( test.hs, interpreted )
Ok, modules loaded: Main.
[*Main] > main
"hello, world"

Much better!

You can inspect a value (or function) in ghci with the `:info` command
in order to find out a little about its type and definition:

ghci> :info main
main :: IO ()   -- Defined at test.hs:1:1

If you just wish to see the type of an expresison, you can use
the `:type` command:

ghci> :type main
main :: IO ()


* In the previous example, you defined a function 'main'
  that printed "hello, world"...
* .. Now, define a new numeric function that prints something else
* Load it in GHCi
* Test your function in GHCi
* Make a modification
* Reload your chages without exiting GHCi
* Test your changes

GHC

Create the following source file (program.hs):

main = print "hello world"

Compile the program as follows:

stack exec -- ghc --make program.hs

Run the program with the following command:

./program

The output should look as follows:

"hello world"
Compiled programs are almost always significantly faster than instructions
run inside GHCi. Even greater speed-ups are possible by using the "-O"
optimisation settings for GHC.
Using GHC...

Compile and run hello-world.
> echo 'main = print "hello friends"' > main.hs
> stack exec -- ghc --make main.hs
[1 of 1] Compiling Main             ( main.hs, main.o )
Linking main ...
> ./main
"hello friends"
An open-ended question:

Given that GHC is largely written in Haskell, how was GHC first compiled?
An open-ended question:

What are some of the current issues with the Haskell ecosystem?

Ecosystem

The Haskell ecosystem is large and interesting, it is held together more by convention than by dictation, with the current convention being that open source packages are made available through cabal on Hackage. On top of this distribution, there is a convenient tool provided by Commercial-Haskell called Stack. Stack builds off the existing ecosystem, but provides stable snapshot releases of compatible packages that makes it easy to install packages that play well together.

Lexicon

----------- ------------- ------------
Stack install pointfree
ghci new Hackage

Stack

The easiest way for newcomers to get started with Haskell these days is by installing Stack via the steps outlined in the Setup chapter.

Stack provides a plethora of functionality and you can get an inkling of this by invoking stack --help. However, for the purposes of this workshop you will only really require the use of stack exec --ghci.

The next steps to take would be the installation of libraries and programs via stack install and the creation of new stack projects via stack new.



Install the pointfree package from stack.

Use the `pointfree` command-line program to to check what the
pointfree version of `\x -> \y -> x + y + 1` is.

Did this seem pointless to you?
$ pointfree '\x -> \y -> x + y + 1'

flip flip 1 . ((+) .) . (+)
An open-ended question:

How would you go about publishing your own package to Hackage?

Introduction

The following exercises are intended to be used to warm up your fingers, rather than your brain. These should be run through quickly to get you used to using your development environment.

The majority of your workflow should be performed by writing code in an editor, and testing it in GHCi. You will write the definitions in the editor and test them with simple expressions in GHCi.

Lexicon

----------- ------------- ------------
Primitives Prelude Variables
Literals let Definitions
String Tuples Functions
Invocation Lists Infix
Cons (:) []
Destructuring Pattern-Matching ` head`
Partial length ` map`
Expressiveness

Primitives

Haskell comes pre-packaged with many primitives available in the Prelude module that is included by default, but you should at least make yourself familiar with the following types, and literal syntax:

What? Type Literal Syntax
Machine Ints Int 42
Strings String, [Char] "Hello World"
Booleans Bool True , False
You can type any literal directly into GHCi in order to have it echoed
right back at you. This may be useful for sanity checking that you have
the syntax right!

ghci> 42
42

Variables

In Haskell you can define a variable with the = sign.

Variables can be defined at the top-level (no-indentation):

myVariable = 2

Variable names should start with a lowercase letter and contain no spaces, or special characters, besides underscores, numbers, and '.

If you wish to define a variable inside GHCi, you have to prefix
the definition with "let"... For example:

[Prelude] > let myName = "Simon"

Some examples of variable names are:

  • a
  • my_name
  • data43'
Define your own variable.
x = "hello"
What is an example of an invalid variable name?
invalid-variable = 123

String literals look familiar:

myString = "hello world"
Define a variable containing a string.

Tuples

Tuples look like this:

myTuplePair = (1,"hello")

myTupleTrio = (1,"hello",3)

They can be used to group multiple, differently-typed (heterogeneous) values.

Define a variable containing a tuple.

Functions

Functions are a core part of Haskell. Function definition and invocation look like this:

-- Definition:
myFunction x y ... = ...

-- Invocation:
... myFunction 1 2 ...

This is different to what you might be familiar from a c-familiy language such as Javascript:

// Definition:
function javascriptFunction(a,b ...) { ... }

// Invocation:
javascriptFunction(1,2)

For example:

myAdd x y = x + y

myAdd takes two numbers and returns the result of the addition of those two numbers.

Define a function `myMultiply` that multiplies 3 numbers.
myMultiply x y z = x * y * z
Use your `myMultiply` function to multiply `4`, `5` and `6734`.
Prelude> myMultiply 4 5 6734

Lists

List are a commonly used data-structure in Haskell. Everything in a list has the same type (they are homogeneous).

Lists are built using the infix data-constructor (:) (pronounced "cons"). They also have a compact notation using [...].

List literals look like:

list1 = [1,2,3]
list2 = 1 : 2 : []
list3 = "hello" : "world" : []

More information about why lists can be used the way that they are is contained in the ADTs chapter.

Define a variable containing a list.
myList = [1,2,3]

You can deconstruct a list by pattern matching the head and tail like so:

f (x:xs) = ...
Define a function to get the first element of a list.
myHead (x:xs) = x -- This is a partial function, Beware!

In Prelude this function is called head.

"head" is a partial function - It will raise an exception if
called with an empty list.

In Haskell we generally wish to avoid defining partial functions.
Define a variable containing the first element of your list.
myFirstElement = myHead myList

Define Length

Define a function that takes a list and returns the length.

Your solution should have the form of:

myLength []     = ...
myLength (x:xs) = ...

Things to consider:

  • What is the length of an empty list? (the base case)
  • What is the length of xs?
myLength []     = 0
myLength (x:xs) = 1 + myLength xs

Define myMap


  
Define a function that takes a function from a to b,
and a list of 'a', and returns a list of 'b's.

Some concrete examples of such a function may do the following:

  • Take a function that divides integers by two, list of ints, and returns a list of doubles.
  • Take a function that turns lowercase into uppercase characters, and a String, and returns a string in CAPS. Such an uppercasing function can be found in the Data.Char module named toUpper.

Things to consider:

  • What is the base-case of myMap?
  • What is the inductive-case of myMap?
myMap f [] = []
myMap f (x:xs) = f x : myMap f xs

Lists can be combine by using the ++ function. This is an infix function similar to +.

Combine your list with itself to make it twice as good!
betterList = myList ++ myList

Fun List Functions

For your reading pleasure, here are some definintions of common functions:

myFilter f []     = []
myFilter f (x:xs) = if f x then x : myFilter f xs
                           else     myFilter f xs

myFold f z []     = z
myFold f z (x:xs) = f x (myFold f z xs)

myReverse []     = []
myReverse (x:xs) = myReverse xs ++ [x]

myElem e []     = False
myElem e (x:xs) = if e == x then True
                            else myElem e xs
An open-ended question:

What is a good balance between safety and expressiveness in a
programming-language?

Types

Question: How do you create a great program?
Answer:   You type it!

In this chapter, we will go over the exercises from the introduction and add types to the examples.

Lexicon

----------- ------------- ------------
Type Signature Inline
Int :: Floating
Variable Synonym String
Tuple Function (,)
C Argument Curried
Parentheses Multiply Lists
Around-Fix Prefix-Form Deconstruction
head length map

Signatures

In Haskell, type signatures can be provided inline, or above definitions.

For example:

x :: Int
x = 3

or

x = (3 :: Int)

It is far more common to place the type-signature above the definition, with inline types only used in situations where ambiguities need to be resolved.

You are defining a floating point variable:
myFloat = 1.1
Give your variable a type-signature.
myFloat :: Float
myFloat = 1.1

Type Synonyms

In Haskell, we can give type-expressions an alias (or synonym) by using the type keyword. This allows you to cut down the verbosity and chance of errors in your code when you have type expressions that would otherwise be repeated frequently.

An example of this is the String type-synonym, which is defined as follows:

type String = [Char]
 

Give your string variable from the previous chapter a type-signature.
myString :: String
myString = "Hello Haskell"

Tuples

Tuple type signatures look the same as the tuples themselves, with types in place of the data.

For example, if you had a tuple of a String and an Int, the type would look as follows:

myTuple :: (String, Int)
myTuple = ("The meaning of life", 42)
Give your previous tuple definition a type signature.

Functions

The type signatures of functions in Haskell are a little different from how they look in the more familiar C family languages, but the syntax is very elegant, and will allow a higher-level of reasoning than less consistent forms.

The syntax for a function type-signature is of the form:

{functionName} :: {argument} -> {result}

The main idea is that functions in Haskell only ever take one argument. If you wish to define a function that takes more than one argument, then you should, in fact, define a function that takes one argument, then returns another function.

Luckily the syntax for doing this in Haskell looks identical to defining a multi-argument function:

myMultiply x y z = x * y * z

However, the distinction becomes clear with the type-signature:

myMultiply :: Int -> (Int -> (Int -> Int))
myMultiply x y z = x * y * z

Now we can see that the function only takes one argument, then returns a function (that only takes one argument, and returns a function (that only takes one argument, that returns an Int.))

This is known as currying.

Fortunately, Haskell's function syntax is right-associative, allowing us to drop the parentheses:

myMultiply :: Int -> Int -> Int -> Int
myMultiply x y z = x * y * z
 

Define a function `myMultiply` that multiplies 4 numbers.
Give your function a type-signature
myMultiply :: Int -> Int -> Int -> Int -> Int
myMultiply w x y z = w * x * y * z

Lists

List type-signatures look like:

list1 :: [Int]
list2 :: [Int]
list3 :: [String]

list1 = [1,2,3]
list2 = 1 : 2 : []
list3 = "hello" : "world" : []

list1A :: ([]) Int
list1A = [1]

List type signatures are special in that the type-constructor is "Around"-fix. This is not generally possible, and lists are a special case in that regard.

If you find you need to, you can use the list type in prefix-form, as per variable list1A.

Define a list variable and give it a type-signature.
myList :: [Int]
myList = [1,2,3]
Give your `head` deconstructor function a type-signature.
myHead :: [a] -> a
myHead (x:xs) = x

Length Signature

Give your length function a type-signature.
myLength :: [a] -> Int
myLength []     = 0
myLength (x:xs) = 1 + myLength xs

Map Signature

Give your `map` function a type-signature.

Things to consider:

  • What is the type of the first argument of myMap?
  • What is the second argument, etc?
  • What is the type of the result of myMap?
myMap :: (a -> b) -> [a] -> [b]
myMap f [] = []
myMap f (x:xs) = f x : myMap f xs

Fun List Functions Types

Here are the types for the definintions of the list functions from the previous chapter:

myFilter :: (a -> Bool) -> [a] -> [a]
myFilter f []     = []
myFilter f (x:xs) = if f x then x : myFilter f xs
                           else     myFilter f xs

myFold :: (a -> b -> b) -> b -> [a] -> b
myFold f z []     = z
myFold f z (x:xs) = f x (myFold f z xs)

myReverse :: [a] -> [a]
myReverse []     = []
myReverse (x:xs) = myReverse xs ++ [x]

myElem :: Eq a => a -> [a] -> Bool
myElem e []     = False
myElem e (x:xs) = if e == x then True
                            else myElem e xs
Try to understand the type-signatures for these functions.

Hint: Try finding a way to say them in English.
An open-ended question:

How many types could a type-checker check...
... if a type checker could check types?

ADTs (Algebraic Data Types)

Algebraic Data Types are THE bread and butter of Haskell programs.

  • Functions evaluate data by pattern-matching against ADTs
  • Problem-domains are modeled using ADTs
  • Laziness is linked to ADTs
  • Types can be derived from ADT definitions

But how does that help me?

Lexicon

----------- ------------- ------------
ADT Algebraic Laziness
Types Modelling Bool
Enum C++ Parameter
Constructor Recursive Concrete
Kind * String
Int Maybe []
IO (->) Deriving
data Show Type-Class
List

Example

An example of an ADT in Haskell:

data MyBool = MyTrue | MyFalse | MyNotSure

should_I_eat_something_bigger_than_my_own_head :: MyBool
should_I_eat_something_bigger_than_my_own_head = MyFalse
With this functionality, you are able to introduce your own "Enum"
values.

The MyBool example is somewhat equivalent to the following C++ code:

enum MyBool { MyTrue, MyFalse, MyNotSure };

With the added bonus of not having out-of-bounds casting ruin your day.

If your problem space can be modeled using various discrete values,
then this form of ADT will allow you to create a program that mirrors
your problem!

You can add parameters to the data constructors:

data MyNullString = Nada | MyString String

stringy :: MyNullString
stringy = MyString "Whatever, It's just a string"

blanky :: MyNullString
blanky = Nada

Constructors can take multiple parameters:

data SomeJunk = Rubbish String | TrashPile String Int Bool

discards :: SomeJunk
discards = TrashPile "Junk Yard" 42 True

Furthermore, ADTs can be recursive:

data MyNumberList = Nada | SomeNumbers Int MyNumberList

numbers :: MyNumberList
numbers =  SomeNumbers 1 (SomeNumbers 2 Nada)

Finally, ADTs can be parametrised by other types:

data MyContainer x = Jar x

contained :: MyContainer Int
contained = Jar 1

pun :: MyContainer (MyContainer String)
pun = Jar (Jar "Binks")

In general, the syntax of an ADT looks similar to the following:

ADT          := data <TypeName> <Variables> = <Constructors>
TypeName     := [A-Z] + [a-z'_]*
Parameters   := <ConcreteType> + (" " + <ConcreteType>)*
Constructors := <Constructor> + (" | " + <Constructor>)*
Constructor  := <TypeName> + <Parameters>
Variables    := <Variable> + (" " + <Variable>)*
Variable     := [a-z] + [a-z'_]*

ConcreteType can't be defined syntactically, but it means that your type is "Fully Applied" (in Haskell terms, of kind *). An example of some concrete types are:

  • String
  • Int
  • Maybe String
  • [Int]

Examples of some non-concrete types are:

  • Maybe
  • IO
  • (->)

Deriving

One final thing to note is that in order to be able to print values of your data types at the GHCi REPL, you will need your data to be a member of the Show type-class.

Type-classes are covered in depth in the type-classes chapter.

This is achieved by appending the following text after your data definition:

data MyData = SuperGreat deriving (Show)

Similar classes are needed for ordering and equality. If you get stuck on a missing class, just add the following deriving triple for now:

data MyData = SuperGreat deriving (Eq, Ord, Show)

Exercises

With all of this power at your disposal, it's time to define a list ADT yourself.

Define your own generic list ADT.

Things to consider:

  • Should this ADT be parametrised?
  • Should this ADT be recursive?
  • Should this ADT have multiple constructors?
  • Should the constructors be parametrised?
data MyList a = Empty | Items a (MyList a)
An open-ended question:

What would the ADT for a Lisp-like language look like?

Type Classes

A big part of writing clean reusable code is controlled polymorphism. We are like a carny at the roller-coaster, anybody can ride the roller coaster, but you must be at least this tall.

In the object oriented world we use the inheritance heirachy, we know that if something is a subclass, it has at least all of the features of its parent. Of course this is all intertwined with the sharing of data and state, and that's bad.

Lexicon

----------- ------------- ------------
Polymorphism Object-Oriented Inheritance
Data State Type-Classes
Functions Context =>
Num (+) Show
Read String return
Dispatch (::) Definition
Instance Declaration

Functional Type-Classes

In the functional world we get type classes, which is just controlled polymorphism without the baggage. They basically say, that I don't need to know exactly what type you'll call me with but you need to provide a bunch of functions for me to use.

A function tells you what type classes it needs with a "context", The context is the bit to the left of the double arrow "=>"

(+) :: Num a => a -> a -> a

show :: Show a => a -> String

In the above example, we have (+). It can work for any type that is a number. There are built in types for numbers and you can also define your own.

show can turn any "Showable" type into a string. this is analogous to the toString() method in Java.

Define a function that adds one to everything in a list.
What is the type of this function?
read :: Read a => String -> a

incrementAndshow :: (Num a, Show a) => a -> String

Unlike most other languages, with some kind of type driven function dispatch (e.g. methods, overloading). Haskell can also use the return type of the function to choose functionality, this can take a little getting used to, but it is powerful.

read turns a string into a value of another type, although we don't know what this is yet. It depends, for example on the type of the function that you are passing the result of read into.

incrementAndShow demonstrates a function that uses two type classes in its context.

In ghci, convert a string to an integer using read, then covert
a string into a list of integers using read.

(Hint: use (::) to add a type to an expression)

If you just type 'read "1"' in ghci you get an
error, why is this?
Define `incrementAndShow` which adds one to a number and
coverts it to a string.
What is they type of this function?
How did haskell infer your context?

Defining Your Own Type Classes

Let's define a type class of things that can be rated, as in 1 to 5 stars or awesome to lame.


data Rating = 
  SoAwesomeICried |
  PrettyCool      |
  Meh             |
  ForTheLoveOfGodMakeItEnd
  deriving Show

class Rateable r where
  rating :: r -> Rating


data Beer = Coopers | Fosters | CarltonDraught
  deriving Show

instance Rateable Beer where
  rating Coopers = PrettyCool
  rating Fosters = ForTheLoveOfGodMakeItEnd
  rating CarltonDraught = Meh


data Movie = Movie String
  deriving Show

instance Rateable Movie where
  rating (Movie "Bladerunner") = SoAwesomeICried
  rating (Movie "Tron") = PrettyCool
  rating _ = Meh

When we define a type class we write out the name and the types of the functions that we want associated with this type class. We also put a type variable in our definition which refers to whatever type may instance this type class later. We can use this type variable in the definitions below. At this point we can also define default definitions for the functions.

We then define two new data types, Beer and Movie for which we add an "instance declaration" this is where we write the definitions of the type class functions specialized for beers of movies.

We now know how to manually add type class definitions and we could add custom Show instances for each of our new datatypes. However this would be a bunch of boilerplate nonsense, so we can use the handy deriving directive to automatically add a Show instance for us.

Create your own Rateable type class and wacky Rating datatype.
Then create datatypes and instances for something you have a
strong opinion about, like cars or a political party.
Add a review function to your type class that returns
a short textual review.
Add a Rateable instance for a native type, like String.

There are a few other cool things you can do with typeclasses that are not covered here. So if you want to know more, check out some of these other articles:

http://www.haskell.org/tutorial/classes.html

Monads

Lexicon

----------- ------------- ------------
Monad Rules Side-Effects
Tainted Function IO
Bind (>>=) Specialised
main getLine Isomorphic
Inside Unwrapped "and-then"
return lift Vanilla
Interleaved do <-
DSL Desugar AI
Pure sequence forever
mapM CQRS Transform
Compose Explicit

No seriously, what are Monads?

A monad is just a bunch of rules. There are many, many analogies for monads out there. Each of these analogies are useful, but can be obscuring on their own, they are just one view point. To effectively wield monads we must use many different view points together, each one revealing a little piece of the underlying structure of monads. Each view point becomes another tool in our mental toolbox.

So there is no one magic-bullet analogy for monads, only many complementary ones.

Haskell uses monads to represent side-effects. The simplist and most practical analogy is the "tainted value" analogy. In haskell the function that reads a file looks like this:

readFile :: FilePath -> IO String

This function can't return a normal string, because the return value has been tainted by side effects. So the IO monad is acting as a tag saying that the returned string must be treated specially.

But an IO String is not very useful to us, because we want to actually do things with it. So Haskell, in its normal paternalistic style, allows us access the String inside an IO String only in a very careful way. We use an operation call bind (>>=) to access the string.

(>>=) :: Monad m => m a -> (a -> m b) -> m b

-- Here specialized for IO and String

(>>=) :: IO String -> (String -> IO b) -> IO b

This says the only way we can "look inside" an IO String is by providing a function that takes a String and returns some other new value that has also been tainted by the outside world. In other words we can only look at a value "inside" the IO monad if we promise to make sure our result will also be tainted.

This means that if some code uses a value from the outside world, even deep inside, it cannot be hidden, it must be made explicit in the type. Tainting is a one way street, once you are tainted you can't be untainted. There is no function untaint :: IO a -> a. One can't get an untainted value from a tainted one.

In fact, in haskell, the very way we construct a useful program is by ultimately creating value of type IO () that we assign to special variable called main.



Why can't one write untaint?

If you could what problems would this cause?

One thing that can be a little strange is the type of getLine. In imperative languages, functions of zero arguments make some sense. They can be thought of recipies or to-do lists. In haskell a total function of type () -> Foo is isomorphic to a constant Foo. Because the function only has one input value and therefore only one possible output value.

So let us return to getLine. In an imperative language it would look like getLine :: () -> String. Our first problem is that the return value of this function is tainted by the outside world, so we need to use the IO monad, getLine :: () -> IO String. Now because of the isomorphism between unit domian functions and constants we can just write getLine :: IO String. We call a constant of type IO a an "IO action". Because it stands for a bunch of side effects that will be performed together.

This will seem strange at first, because getLine isn't really a function -- it's just a constant value! But that's okay because while the IO String is a constant (i.e. there is only one distinct IO action that reads a line from the console) the value inside the monad is not constant. It can be different each time we look inside.

> getLine
hello
"hello"

> getLine
monad
"monad"

> getLine >>= (\name -> putStrLn ("Hello " ++ name))
andy
Hello andy

One thing leads to another

Often when doing IO one wants to make sure one thing happens after another, We can use (>>=) and just ignore the unwrapped value:

putStr "Hello " >>= (\_ -> putStrLn "World")

putStrLn "One" >>= (\_ -> putStrLn "Two") >>= (\_ -> putStrLn "Three")

This pattern can be easily abstracted, it has been standardized as (>>) :: IO a -> IO b -> IO b. This can be read as "and then".

putStr "Hello " >> putStrLn "World"

putStrLn "One" >> putStrLn "Two" >> putStrLn "Three"


Write a program that prints something stupid, funny or rude.

Make sure to use (>>) somewhere.

Monad Wars III: Return of the Value

We mentioned before that there is no way to untaint a value, once it has been tainted, we can make new tainted values from it, but never untainted ones. But that begs the question, can we choose to taint a value? Indeed we can, in fact, this is a fundamental operation of a Monad. In haskell it is confusingly called return :: a -> IO a.

In recent versions of GHC (>= 7.10), Monads must also be
Applicative, with the default implementation of `return`
being Applicative's `pure`.

A common pattern is to "open up" an IO with bind (>>=), mess around with the contents then wrap it back up again with return. We have a function to help us with this called lift. Specialized for IO it has type lift :: (a -> b) -> (IO a -> IO b). We can use this to take a vanilla function and "lift" it into the IO monad. It works by unwrapping the IO monad calling our function and then wrapping the result back up again.

use return to write 'myLiftIO :: (a -> b) -> IO a -> IO b'

Do it, do it good.

Sometimes when you are doing a lot of ad-hoc interleaved IO, using bind and return all over the place can become a pain in the fingers. So haskell provides special syntax for using monads, called "do notation".

To use do notation, we start a "do block" with the keyword do. Inside a do block, statements can be written line by line and they are automatically joined using "and then" (>>). This causes them to be run one after the other like in an imperative programming language. One can also easily unwrap monad values and bind them to a variable with using a left arrow "<-" syntax.

  main = do
    putStrLn "Hello World"
    putStrLn "Enter your name: "
    name <- getLine
    putStrLn ("Hello: " ++ name)

The do-notation is like a DSL, under the hood it is just expanded to a chain of bind (>>=) calls. Here is what the above code would look like without do-notation:

  main  =
    putStrLn "Hello World" >>
    putStrLn "Enter your name: " >>
    getLine >>= (\ name ->
      putStrLn ("Hello: " ++ name))


Write a program that asks for someone's first and second name,
then complements them (or makes fun of them).

For extra points ask for their age, and customize the complement
(or insult) depending on how old they are.

Do it with do-notation first, then "desugar" it into raw bind (>>=) calls


My father once told me about an "amazing AI" that his
magician/programmer friend build, which could answer any yes/no
question as well as a human.

Of course it only worked if his friend was the one typing
the question! The trick being that it just counted the
number of spaces in the question. If there was an even number
it would output true, if there was an odd number, false.
You just fiddled with the wording, or snuck in an extra space,
to get the answer that you wanted...

Looks like it's time to write your super-dooper
human-level AI and see if your friends can figure
out how it works.

Stay Functional, San Diego

Even when we are programming with side effects, we still want our programs to follow functional principals. To stop our code ending up like C written in do-notation, there are some principals we can try to follow:

  1. Try to do most of the work inside pure functions.

  2. Don't create an IO action until the last possible moment.

  3. Be declarative, think of program as declaring a pipeline or specifying an interaction, instead of being a to-do list.

  4. Avoid mixing control, state and calculation. Instead abstract them into small composable pieces. For inspiration see the monad combinators in Control.Monad (e.g. sequence, forever, mapM).

These principals are not specific to monads. They are applicable to all side-effect heavy programing problems. These principles can also be applied to imperative programs for great justice.

A lot of patterns like reactive programming, dataflow programming and CQRS are consequences of following principals such as these.

The pithy take-away is don't "update, execute, update". Instead "represent, transform, compose".

An open-ended question:

Why is it a good idea to make side effects explicit?

Let's Make a Guessing Game

The Game

Your task is to make a guessing game where the computer comes up with a number, and you have to make guesses - with the computer telling you if you were too high, or too low.

Lexicon

----------- ------------- ------------
Game Stack main
print System.Random randomRIO
readLn compare case
do Procedure

Example Session

$ stack exec -- runhaskell Module_1470214643_41323.hs
"Let's play the number guessing game"
"Enter a number"
2
"Too low :("
"Enter a number"
8
"Too high :("
"Enter a number"
5
"Too high :("
"Enter a number"
4
"You win!"

Solution Toolbox

In order to solve the problem the following should be used:

--- | --- Tool | Details main | You must define a main function in your module print | Lets you print things to the console System.Random | import System.Random to get access randomRIO randomRIO | Generates a random number from a range as an IO action readLn | read a value from the console as a number compare | compare x y tells you if x is LT, EQ, or GT y case | Dispatch on a value do | Execute multiple IO actions

Thinking Functionally

Try to build the solution piece-by-piece. Examine how you can use recursion in conjunction with pattern-matching to keep re-running a procedure until a condition is satisfied.

Solution

If you're having trouble coming up with the solution, then here are some hints!

Main

main :: IO ()
main = do
    print "Let's play the number guessing game"
    n <- randomRIO (1, 10)
    game n

Game

game :: Int -> IO ()
game n = do
    print "Enter a number"
    g <- readLn
    case compare g n
      of LT -> tooLow  n
         GT -> tooHigh n
         EQ -> print "You win!"

Too Low

tooLow :: Int -> IO ()
tooLow n = do
  print "Too low :("
  game n

Full Solution

import System.Random

main :: IO ()
main = do
    print "Let's play the number guessing game"
    n <- randomRIO (1, 10)
    game n

game :: Int -> IO ()
game n = do
    print "Enter a number"
    g <- readLn
    case compare g n
      of LT -> tooLow  n
         GT -> tooHigh n
         EQ -> print "You win!"

tooLow :: Int -> IO ()
tooLow n = do
  print "Too low :("
  game n

tooHigh :: Int -> IO ()
tooHigh n = do
  print "Too high :("
  game n