You can download Haskell here.
You can often also install it by installing the haskell-platform
package with your favorite package manager.
GHC (the Glasgow Haskell Compiler) comes with an interactive interpreter. You start the interpreter by running ghci
at the command line.
The code you write at the interpreter has very similar syntax to regular Haskell code. The biggest differences are that you start function definitions with let
and you have to split up multi-line statements with semicolons instead of newlines.
To follow along, I recommend starting GHCi now.
Once in GHCi, run the following:
:set prompt "> "
That changes GHCi's prompt to be less clutter-y.
Try entering
5
into GHCi. You will see that it prints the number "5".
When you type a number into GHCi, it will interpret that number as a Float, Integer, Int32, etc. depending on the context. By default, GHCi interprets numbers as Integers.
We can make it clear from the context that GHCi should interpret the number as a float. Try typing
5 :: Float
<Value> :: <Type>
means "Value
has type Type
", so we're telling GHCi that 5
is a float here.
As you can see, GHCi now prints "5.0" instead of "5".
Now let's try assigning that value to a variable.
let x = 5 :: Float
Now, if you enter
x
GHCi will print "5.0".
If we don't know the type of something, we can ask GHCi. Try typing
:type x
GHCi should tell you that x :: Float
(meaning, again, that x
is a Float
).
Haskell has some built-in number types. The most common ones are Float
, Double
, Int
, and Integer
. Int
is fixed-size (usually 64-bit) while Integer
is arbitrary-precision (like Java's BigInteger
).
There are also unsigned int types available in the Data.Word
package.
In some languages, you can add numbers of different types and the language will implicitly cast those numbers to the same type before adding them. Not so in Haskell. You can only add/subtract/multiply/etc. numbers of the same type.
For example, try running
(1 :: Float) + (1 :: Integer)
You will get an error.
Remember how I said that, if we type a number into GHCi, it will guess the type of the number based on the context? Let's test that out.
let y = x + 2
Remember that x :: Float
. Let's ask GHCi the type of y
.
:type y
As you might have guessed, y :: Float
as well. That means that GHCi recognized from the fact that x :: Float
that 2
must be interpreted as a Float
as well. Of course, the result of adding two Float
s is another Float
.
We have a number of functions to convert between number types. Probably the most useful ones are fromInteger
and toInteger
. fromInteger
converts an integer to any other type of number, and toInteger
converts whole numbers (Int, Int32, etc.) to Integers.
Let's try writing a function on a number.
let double x = x * (2 :: Int)
You may be confused by this syntax; Haskell doesn't put parenthesis around function arguments. You just write them after the function name, separated by spaces.
Try running
double 5
As you may have guessed, the result is 10. If we try again with
double 5.0
we get an error, because we're trying to multiply a Float by an Int.
Let's try checking the type of double
.
:type double
GHCi tells us double :: Int -> Int
, which means "double
takes an Int
and returns an Int
".
Let's try writing a function to square a number.
square x = x * x
If we square an Int
, it should return an Int
, and if we square a Float
, it should return a Float
. Let's try it:
square 5
square 5.0
As you can see, square 5
returns 25
, while square 5.0
returns 25.0
.
Let's check the type of square
.
:type square
GHCi says square :: Num a => a -> a
. Notice the thick arrow and the thin arrow. This means "square
takes an a
and returns an a
, as long as a
is a Num
". In other words, this function isn't restricted to one particular type of number; it will take and return any type of number.
Note that square
won't ever take an Int
and return a Float
; it will always return the same type of number that it takes. It is simply capable of taking (and returning) different types of numbers depending on the context.
We can also write functions using lambda syntax. To implement square
using lambdas, we write it as
let square = \x -> x * x
This does exactly the same thing as the earlier implementation. Lambdas are useful because they let us implement functions without naming them. We'll see how that can make our programs simpler and shorter in a little bit.
Lists are one of the most fundamental data structures in Haskell.
Haskell Lists are distinct from C or Java arrays, Python lists, etc. Because Haskell is a pure functional language, it doesn't allow us (generally speaking) to change the value of a variable after it's been assigned. Therefore, if we wanted to update a single element in a C-style array, we'd have to copy the whole array (which would obviously be very slow).
Haskell Lists are linked-list data structures. That is, each list element is a piece of data and a pointer to the rest of the list. An empty list is represented by []
.
Let's do some stuff with lists.
let nums = 1 : (2 : (3 : []))
:
(pronounced "Cons") takes a piece of data (on the left-hand side) and a list (on the right-hand side) and turns it into a linked-list element.
This is exactly the same as writing
let nums = [1,2,3]
People generally use the latter syntax. Try printing out nums
.
nums
GHCi uses the latter representation and prints "[1,2,3]"
Try this:
0 : nums
As you can see, this simply prepends 0
to the front of the list. Note that, because this is a Linked List, prepending (and popping) are very fast, as opposed to in most languages. The downside of using Linked Lists is that indexing is very slow, so we have to use other data structures for that.
Thankfully, because of Haskell's functional style, it's very easy for us to do almost everything without indexing. For example, let's say we wanted to double all the numbers in a list. Instead of having a for loop and multiplying each number by two, we can use map
.
let double x = x * 2
map double [1,2,3]
As you can see, the result of this is [2,4,6]
. Note that map
doesn't modify the input list; it creates a brand new list with the map operation applied.
We can re-write this to be a lot shorter using lambda functions.
map (\x -> x * 2) [1,2,3]
Because we only use this "doubling" function once, we don't really need to give it a name, so we can eliminate a line of code by writing it as a lambda function.
Haskell has a list enumeration syntax that's pretty useful for generating sequential lists of things.
let positiveInts = [1..]
let firstTenPositive = [1..10]
let firstTenEven = [2,4..20]
let alphabet = ['a'..'z']
Haskell has list comprehensions. If you've done some Python programming, you'll be familiar with these.
let columns = ['a'..'f']
let rows = [1..6]
let coords = [(column, row) | column <- columns, row <- rows]
Haskell has a few commonly used built-in functions on lists.
head [1,2,3] = 1
tail [1,2,3] = [2,3]
take 2 [7,8,9] = [7,8]
drop 2 [7,8,9] = [9]
sum [2,3,4] = 9
product [2,3,4] = 24
zip [1,2,3] "abc" = [(1,'a'),(2,'b'),(3,'c')]
unzip [(1,'a'),(2,'b'),(3,'c')] = ([1,2,3],"abc")
filter (/= 2) [1,2,3] = [1,3]
Haskell built-in String
s are simply lists of Char
s. We can even write
'H' : "ello World"
to get "Hello World"
. You can do anything to a String
that you can do to any other list, including mapping.
Some useful functions that work on String
s (and lists) are (++) :: [a] -> [a] -> [a]
, for concatenation, and length :: [a] -> Int
for length.
For example,
length ("Hello" ++ " " ++ "World")
Now, you may still be wondering why Haskell uses the syntax
plus a b = a + b
instead of
plustuple(a,b) = a + b
You actually can write it the second way. The type in that case is plustuple :: Num a => (a, a) -> a
That is, it takes a tuple of two numbers and returns another number.
Why, then do we choose to write functions in the style of plus :: Num a => a -> a -> a
?
This decision has its mathematical basis in currying, but it has some very simple practical benefits.
Note that we can re-interpret the type of plus
as Num a => a -> (a -> a)
. This means that, instead of thinking of plus
as a function that "takes two numbers of type a
and returns another number", we can think of it as a function that "takes a number of type a
and returns a function of type a -> a
". So we can re-write
plus 5 6
as
(plus 5) 6
These mean exactly the same thing.
As you can see, if we only give it one argument (5
), plus
simply returns another function that we can apply to the second argument (6
).
Let's try assigning (plus 5)
to a variable.
let plus5 = plus 5
:type plus5
As you can see, plus5 :: Num a => a -> a
. We can then type
plus5 10
to add 5 to 10.
The nice thing about this is that we only need to apply as many arguments as we want; we're not obligated to fill out all the arguments. On the other hand, if we were using tuples for arguments, we would have to fill out all the arguments at once.
To see how this can be useful in practice, let's look at the type of map
.
:type map
map :: (a -> b) -> [a] -> [b]
. In other words, it takes a function from a
to b
and a list of a
s, and it returns a list of b
s.
Or, alternatively, we can interpret this as map :: (a -> b) -> ([a] -> [b])
. That is, we can use map
to turn a function from a
to b
into a function from a list of a
s to a list of b
s. For example,
let doubleItems = map (\x -> x * 2)
Because we only filled out the first argument of map
, we now have a function that takes a list as an argument. You can verify this by checking that the type of doubleItems
is doubleItems :: Num a => [a] -> [a]
We could also write this as
let doubleItems list = map (\x -> x * 2) list
but this adds unnecessary clutter.
Now we know that we don't have to fill in all the arguments for a function. But aren't mathematical operators (like *
) just functions? Let's try it:
let double = (2*)
It worked! We can partially apply mathematical operators. We just have to put them in parenthesis so the compiler doesn't get confused. In fact, we can partially apply them on either side, so (*2)
is fine too.
Let's re-write our doubleItems
function:
let doubleItems = map (*2)
That's exactly the same as the (much longer and more cluttered)
let doubleItems list = map (\x -> x * 2) list
Sometimes, it's nice to make an infix operator (like +
) into a regular function (like add2
). We can do this by putting the operator in parenthesis.
let six = (+) 2 4
We can also make a regular function into an infix operator by putting backticks around it.
let plus a b = a + b
let six = 2 `plus` 4
See if you can work out (using your knowledge of partial application) why this works:
let plus = (+)
Haskell distributes libraries in the form of modules.
There are some useful functions on []
s that aren't included in the default imports. (FYI, all the default imports are just things from the Prelude
module.)
An example of these functions is nub
, which removes duplicates from a list. For example, nub "hello world"
is "helo wrd"
. We can import it like this:
import Data.List
nub "hello world"
Or, if we only want to import specific functions from the module:
import Data.List (nub)
nub "hello world"
Or, if we want to import everything except nub
,
import Data.List hiding (nub)
Or, if we want to import things so that we have to reference them by library name:
import qualified Data.List as L
L.nub "hello world"
If you'd like more info on a module, you can read the docs or use GHCi's :browse
command.
GHCi is really great for learning something or trying things out, but when we want to write an actual program, we're going to want to compile it.
First, make a file called "Program.hs". It doesn't really matter what you call it as long as the extension is "hs".
If we want to compile this into a program, we need to define "main" just like in C or Java.
Because we're not using GHCi anymore, we don't have to put let
in front of functions anymore. Put this in your ".hs" file:
double = (*2)
main = print (double 5)
Note that main
is just a regular variable, like double
. We'll talk about its type later.
To compile, run ghc Program.hs
, and to run the program, type ./Program
.
Your program should print 10
and exit.
So far we've worked with built-in types like []
and Integer
. How do we make our own types?
Simple! We use a data
declaration. Let's say we want to make a type that held three Int
s and a String
. We can do it like this:
data MyType = ThreeInts Int Int Int String
MyType
is the name of the type (so if we wanted to write a function on it, that's what we'd put in the type signature). ThreeInts
is the "constructor" for that type, which is how we assemble the three Int
s and the String
into a MyType
.
We can make a MyType
like this:
threeFives :: MyType
threeFives = ThreeInts 5 5 5 "It's just three fives!"
Now how do we actually do anything with these values of MyType
? We have to get the contained Int
s and String
out somehow!
Well, one way is with "pattern matching". That works like this:
thirdInt :: MyType -> Int
thirdInt (ThreeInts first second third string) = third
It takes all the values contained in the constructor and assigns them a name, so we can do things with them.
Since we're only using one out of the four contained values, we can shorten our function (and make its purpose clearer) by replacing the unused values with underscores. For example,
thirdInt :: MyType -> Int
thirdInt (ThreeInts _ _ third _) = third
Now, it gets pretty annoying to write out functions to extract a value from a record for every single custom data type you make. Thankfully, there's a way to automate this a bit. If we change our declaration to
data MyType = ThreeInts {firstInt :: Int,
secondInt :: Int,
thirdInt :: Int,
string :: String}
It makes a function thirdInt
that does exactly the same thing as the one we defined manually.
We can also make a new record with a field modified using this syntax:
threeFives = ThreeInts 5 5 5 "It's just three fives!"
twoFives = threeFives {secondInt = 4, string = "Two fives and a four"}
This simply changes the value of secondInt
and string
in threeFives
and saves it to a new record.
It's often very useful to print a record for debugging or display purposes. We could write a function to turn our records into a String
, but that is unnecessarily labor-intensive. Thankfully, Haskell has a way to automate this. We simply have to add deriving (Show)
to the end of our data definition.
data MyType = ThreeInts {firstInt :: Int,
secondInt :: Int,
thirdInt :: Int,
string :: String} deriving (Show)
Now, we can use the function show
to turn a MyType
into a String
, and if we type the name of a MyType
into GHCi, it will print out a nice string representation of it. For example, typing
twoFives
prints ThreeInts {firstInt = 5, secondInt = 4, thirdInt = 5, string = "Two fives and a four"}
.
Now, what if we want MyType
to hold three ints and a String
or two floats and a String
? We can do that!
data MyType = ThreeInts Int Int Int String
| TwoFloats Float Float String
This may look completely alien to you depending on what languages you're familiar with. In languages like Java or Python, a piece of data can have exactly one structure. In some languages, including Haskell, we have something that computer scientists call algebraic data types, which sounds fancy, but just means that pieces of data of the same type can have different structures.
So a MyType
can have either one of those two layouts. If we're writing a function that works on MyType
s, how do you handle the two possibilities? Simple! We use pattern matching.
howManyNumbers :: MyType -> Int
howManyNumbers (ThreeInts _ _ _ _) = 3
howManyNumbers (TwoFloats _ _ _) = 2
We can also pattern match using a case
expression:
howManyNumbers :: MyType -> Int
howManyNumbers record = case record of
ThreeInts _ _ _ _ -> 3
TwoFloats _ _ _ -> 2
The ability for a type to have multiple structures is super useful. You may have heard that Haskell doesn't have null pointers. In many languages, null pointers are used to indicate that the function failed or, for some other reason, couldn't return a value of the type you expected. In Haskell, we can't do that (because null pointers are unsafe). Instead, we can do something like this:
data Maybe a = Just a | Nothing
This is frequently called an "optional type". If a function returns Maybe Int
, it can either return something like Just 5
if it succeeds, or Nothing
if it fails. Because we, the programmer, can see from the function's type that it might return Nothing
, it's explicitly clear that we have to deal with this possibility. This allows us to write equally flexible code while avoiding the null-dereferences crashes that sometimes plague other languages.
Let's make our own version of a list, but for Int
s only.
data List = Cons Int List | Empty deriving (Show)
That is, a List
can either be a Cons
, which has some Int
as well as the rest of the List
, or it can be empty.
Now, these are both equivalent:
1 : (2 : (3 : []))
Cons 1 (Cons 2 (Cons 3 Empty))
We can write functions on our list type:
len :: List -> Int
len Empty = 0
len (Cons _ tail) = 1 + len tail
But our list still isn't as powerful as the built-in list, because we can only put Int
s in it. Well, thankfully there's an easy way to make the type of the list contents variable.
data List a = Cons a (List a) | Empty deriving (Show)
a
is called a "type variable". It lets us vary the type of the contents of the list. For example, if we had a List String
, it would be Cons String (List String) | Empty
.
We can write functions that work on any type of list:
-- We don't care what "a" is
len :: List a -> Int
len Empty = 0
len (Cons _ tail) = 1 + len tail
Or just for certain types of lists:
listConcat :: List String -> String
listConcat Empty = ""
listConcat (Cons str tail) = str ++ listConcat tail
Remember the thing with Num
? Let's explore that a bit more. Look at the type of +
.
:type (+)
(+) :: Num a => a -> a -> a
. Let's re-examine what this means; +
takes 2 things of the same type a
and returns another a
, but a
has to be a Num
. That makes sense. You can add Float
s to Float
s, and Int
s to Int
s, but you can't add Float
s to Int
s or []
s to []
s. (Well, you can concatenate them, but that's not numerical addition.)
How does Haskell know what's a Num
and what's not? The answer is that Num
is a typeclass. Typeclasses are things that describe "classes" of types. For example, numbers are a class of types. (Don't confuse this with e.g. Java's or Python's classes; in Haskell, "class" means what it does in English.) There's more than one number type, but they all share some common operations. It makes sense to define all those operations in one place, so that we can write functions that use those operations on any type of number.
If you're familiar with Java, a typeclass is kind of like an interface. To be a member of a typeclass, you have to implement whatever functions the typeclass requires. Let's look at how this works with Num
. Using Hoogle, we can search for "Num", which brings us here. (Note: Hoogle is a super awesome tool that lets you look Haskell functions up by their type. So if you think "Hmm, what's the name of that function that gives me the first thing in a list?", you can simply search Hoogle for [a] -> a
and it will give you some possible solutions.) As you can see, the documentation for Num
describes a "minimal complete definition" (all the functions we have to implement) as well as their types.
Three of those are as follows:
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
That is to say, if we want to make some type a
a member of the Num
typeclass, we have to write plus, minus, and times for a
.
Let's write out a partial implementation of Num
for tuples of Num
s. You will want to put this in "Program.hs", as typeclass implementations span multiple lines.
instance (Num a, Num b) => Num (a, b) where
(a,b) + (c,d) = (a+c, b+d)
(a,b) - (c,d) = (a-c, b-d)
(a,b) * (c,d) = (a*c, b*d)
The first line means "If a
is a Num
type and b
is a Num
type, then tuples of type (a,b)
are also Num
s." We then have to back up this claim by providing an implementation for all the required functions.
Try loading "Program.hs" into GHCi. You'll get a warning about how we didn't implement all the required functions for Num
, but you can ignore that since we're just experimenting.
Now, if you try running
(1,2) + (3,4)
it will work, because +
works on Num
s, and tuples of numbers are Num
s now!
Let's try making our own typeclass. How about we write one for things that can be mapped over? For example, we can map over a list, but we could also map over something like
data FiveOf a = FiveOf a a a a a
Here's a typeclass for things that can be mapped over:
class Mappable m where
mapIt :: (a -> b) -> m a -> m b
Notice that mapIt
has sort of the same type as map
, but instead of [a] -> [b]
or List a -> List b
or something, it just has m a -> m b
. Therefore, anything that's a member of Mappable
can be used with this function.
Let's first make a Mappable
implementation for List
. Note that --
begins a comment.
instance Mappable List where
mapIt f Empty = Empty -- We don't need to do anything
mapIt f (Cons head tail) = Cons (f head) (mapIt f tail)
Now let's make one for FiveOf
. Remember, the type has to be (a -> b) -> FiveOf a -> FiveOf b
.
instance Mappable FiveOf where
mapIt f (FiveOf a b c d e) = FiveOf (f a) (f b) (f c) (f d) (f e)
Boom! Now we can call mapIt
on a List
or a FiveOf
.
(FYI, there's already a typeclass for this exact thing in Haskell, called Functor.)
There's a lot of mysticism flying around the internet about how Haskell does IO (input/output), like reading files or printing to the console. In reality, it's not all that complicated if you approach it without getting caught up in the theory of it.
Unfortunately, this is one of those things where some explanations work well for some people and not so well for others. Really, once you understand what's going on you'll see that it's very simple. I can't speak for anyone else, but coming from a C/Java/Python/etc. background, Haskell's IO was just very different from anything else I'd used, and it took a bit of getting used to.
Now that we have "Program.hs", we can actually load it into GHCi and play with it. Run (at your terminal, not in GHCi)
ghci Program.hs
Now, everything you've defined in "Program.hs" (like double
) is available in GHCi.
Let's check the type of main
.
:type main
According to GHCi, main :: IO ()
. You should read this as "An I/O action that, when run, returns a ()
". A ()
(pronounced "unit") is just a placeholder value that carries no information. Returning ()
is kind of like returning void
in C or Java. It just means that you don't return anything useful.
This is the part that tripped me up when I was learning about Haskell. You may be asking "Isn't a function of type Int -> ()
also something that, when run, returns a ()
?" The problem here is that many languages don't really distinguish between running and evaluating something, while pure functional languages like Haskell force us to do so.
I will do my best to explain the difference between running (an IO
action) and evaluating (a function).
When you "run" an IO
action, it's allowed to do things that have an effect on the world. For example, it can read a file, or write to the console, or write some data to a pointer. So when we say that an IO Int
"returns" an Int
, it means that if you were to run that IO
action, which might involve reading a file or writing to memory or something, it would end up producing an Int
.
When you "evaluate" a function, it's not allowed to do anything that might have a permanent effect on the world. Obviously, under the hood, it has to do some stuff in memory, but these are temporary effects that aren't observable without a debugger. If you have a function of type Float -> Int
, the only thing it's allowed to do is look at that Float
(or maybe just ignore it) and generate an Int
. It's not allowed to read a file, or connect to the internet, or do anything like that.
This is the part that trips some people up; main
is just a value, like any other value in a Haskell program. The value of main
describes some sort of action that can be run. Us programmers don't know how to actually run these actions; only the compiler knows how to do that. When the compiler makes our program, it looks for the value called main
, and sets up the program so that something (specifically, the Haskell runtime) actually does run the action we've named "main".
Let's try making a Hello World. First, let's take a look at putStrLn
.
:t putStrLn
putStrLn :: String -> IO ()
. That is, it takes a string and returns an IO action. Presumably, if we give it the string "Hello World", it will return an IO action that (when run) will print "Hello World".
Let's try it. In "Program.hs", change main
to
main = putStrLn "Hello World"
and re-compile.
It works! The program ran the IO action that putStrLn "Hello World"
returned.
Let's look at a few other useful IO actions.
:type getLine
getLine :: IO String
. This is the first IO action we've seen that returned something other than ()
. As you may have guessed, IO String
means "an IO action that, when run, returns a String". Again, this is in the second sense of "return", as getLine
is not a function; it only "returns" a string in the sense that we get a String out if we somehow run the action.
At this point you may be asking "If getLine doesn't actually return a string, and is only an action that theoretically generates a string when run, how do I actually do anything with that string?"
This question actually touches on an interesting property of IO actions. There's no way to run an IO action in a regular function. (Well, it's technically possible, but it's only used for writing very low-level libraries.) The only IO action that gets run is main
. However, you can lump multiple IO actions into one IO action, so you can actually run all the IO actions you want by lumping them together and calling it main
. The important thing here is that regular functions cannot run IO actions, which means that regular functions don't have "side effects", like reading a file or writing to console.
To actually answer the question, here's how you can do stuff with the string "returned" whenever we run getLine
:
main = getLine >>= putStrLn
This looks a little daunting, but it's pretty simple. Check the type of >>=
:
:type (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
. OK, so Monad
is a typeclass. Just like +
works on Num
s, >>=
works on Monad
s. I'll talk a bit about Monad
s later, but just know that IO
is a Monad
, so we can use >>=
on IO
actions.
To make things a little simpler to reason about, let's replace m
with IO
(just like how we can replace (+) :: Num a => a -> a -> a
with (+) :: Int -> Int -> Int
if we know the specific type of a
from context).
(>>=) :: IO a -> (a -> IO b) -> IO b
That's a little better!
Now, take a look...
What if we set a
to be String
and b
to be ()
? Then, the type becomes
(>>=) :: IO String -> (String -> IO ()) -> IO ()
Now wait a minute! getLine :: IO String
and putStrLn :: String -> IO ()
. They fit into >>=
perfectly!
The return type of getLine >>= putStrLn
is then IO ()
, which is exactly what we need for main
.
Perfect!
If it helps, you can think of >>=
as creating an action that "runs" the first action, feeds the result into a function that makes a second action, and then runs the second action. This intuition doesn't necessarily hold up for all uses of >>=
, but it works fine if you're just using >>=
to combine IO actions.
By the way, >>=
is pronounced "bind", because it "binds" two actions together.
(If, at this point, you're thinking "Oh God, do I have to use weird functions like >>=
just to read from console?", don't worry! The answer is a definitive "No!". I'll build up to how we actually usually do it.)
OK, so we can now combine certain types of IO actions. Specifically, if we have action a
that returns some value, and a function that takes the value and makes action b
, we can combine them.
What if we wanted to read two lines from input but ignore the first input? Let's think about this.
getLine :: IO String
(>>=) :: m a -> (a -> m b) -> m b
So we can't do getLine >>= getLine
, because the second getLine
doesn't have the correct type. We have to put the second getLine
inside a function somehow. Well, we could just do this:
getLine :: IO String
(\_ -> getLine) :: a -> IO String
That's just a lambda function that ignores the argument. It has the correct type! Let's try it:
getLine >>= (\_ -> getLine)
It works! We've combined two getLine
s, ignoring the result of the first one. It turns out this is a pretty useful trick, so there's a built-in function for it.
(>>) :: Monad m => m a -> m b -> m b
a >> b = a >>= (\_ -> b)
We can also use this to combine multiple IO
actions that "don't return anything" (although they actually just return ()
). For example, we can do
main = putStr "Hello " >> putStrLn "World!"
OK, what if we want to read a line and then print it back twice? Well, we could just duplicate the string and print it once, but let's say for the sake of edification that we want to run putStrLn
twice.
Well, we could do it like this:
main = getLine >>= (\s -> putStrLn s >> putStrLn s)
Man, these things are getting complicated! Surely there's a better way to do this!
Indeed, there is. Fortunately, with a bit of syntactic sugar, we can do all this stuff in a very familiar way. First, notice that getLine >>= \s ...
is basically assigning a value to a variable s
. In procedural languages, that usually looks something like s = getLine()
or s := getLine()
. Let's start by sugaring that:
main = do
s <- getLine
putStrLn s >> putStrLn s
OK! So we've simply moved some things around (and added a do
), but it's pretty obvious how to get back to our original expression that used >>=
.
Now, we can sugar the >>
like this:
main = do
s <- getLine
putStrLn s
putStrLn s
Cool! Looks just like we're used to in imperative languages. The (important) difference is that we're not actually "running" any actions yet; we're actually just (once this gets de-sugared) using >>=
to combine a bunch of IO
values that represent runnable IO
actions. Nothing gets run until the runtime starts running main
. To show what I mean, let's call this something besides main
:
myAction :: IO ()
myAction = do
s <- getLine
putStrLn s
putStrLn s
main = myAction >> myAction >> myAction
Even though myAction
looks like an imperative program, we can pass it around just like any other value and apply functions to it just like any other value. As you can see, when main
gets run, it will run myAction
three times. We could also write
main = do
myAction
myAction
myAction
And that's basically everything you need to know to get started!
I'm going to keep this section brief. I don't think a detailed Monad explanation belongs in a crash course, and I think experience is a much better teacher than explanation in this case.
I said earlier that IO
is a Monad
. What does this mean?
Simple. Just like Num
s support (+) :: Num a => a -> a -> a
, (*) :: Num a => a -> a -> a
, etc., Monad
s support (>>=) :: Monad m => m a -> (a -> m b) -> m b
and return :: Monad m => a -> m a
.
These aren't magical functions or anything. Functional programmers discovered in the 90s that these functions were useful for representing certain things.
I already described how (>>=)
is useful for representing the combination of IO
actions.
Some other common Monad
s are Maybe
and Either
. These monads are usually used to represent functions that can fail.
For example,
failsOnZero :: Int -> Maybe Int
failsOnZero 0 = Nothing
failsOnZero n = Just (n + 1)
failsOnTen :: Int -> Maybe Int
failsOnTen 10 = Nothing
failsOnTen n = Just (n - 1)
doBoth :: Int -> Maybe (Int,Int)
doBoth n = do
a <- failsOnZero n
b <- failsOnTen n
return (a,b)
In this case, if either failsOnZero
or failsOnTen
fails, the entire computation will fail and return Nothing
. The behavior of the Nothing
monad is that if any function combined using (>>=)
returns Nothing
the whole thing returns Nothing
.
I don't expect you to understand exactly how this works yet, but try playing around with this. Try reading Real World Haskell's chapter on error handling for some more in-depth examples.