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
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 namedtoUpper
.
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?
If you wish to learn about why ADTs are "Algebraic", then have a look at:
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:
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:
Try to do most of the work inside pure functions.
Don't create an IO action until the last possible moment.
Be declarative, think of program as declaring a pipeline or specifying an interaction, instead of being a to-do list.
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 |
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