Getting Started with Haskell

Haskell is a purely-functional, lazy, statically-typed, garbage-collected, compiled programming language that pushes the frontiers of programming language design. Get started with it here!

getting started with Haskell

This is me talking about what I think will help people get started with Haskell. If you're interested in category theory and the more theoretical parts of Haskell, I definitely think you should read Category Theory for Programmers by Bartosz Milewski (this isn't a pre-requisite for this blog, just something that you might want to read later).


To get started with Haskell, you need two tools: cabal and ghc (the Glorious Glasgow Haskell Compiler). A very easy way to install ghc is using ghcup. Once that's done, cabal can be installed using ghcup install-cabal.

You're also going to need the language server, which helps a lot during development. If you're going to use VSCode, the installation will happen automatically. If not, check this out.

Note that at the time of writing, the language server supports GHC 8.10.15 (fully) and GHC 9.0.1 (core works, some plugins don't) and other older versions only. It's recommended you use one of these two. You can do this using ghcup install 8.10.15 and ghcup set 8.10.15, for example.

To create a project, create an empty directory, cd into it, and execute cabal init -n.

To run the project, execute cabal new-run.


Haskell is a purely-functional, lazy, statically-typed, garbage-collected, compiled programming language. It gives you a binary that you can execute without needing any kind of runtime installed on your machine, though Haskell binaries are at times quite large since they have all the dependencies and the Haskell runtime statically linked. It is, however, possible to build dynamically-linked binaries, but you then need to provide all the dependencies to the end-user anyway, so a fat binary is usually the way to go. Haskell is very performant and can achieve near-C speeds if written carefully.

Purely Functional

This means that all functions in Haskell take a value and return a value – nothing more. For example, consider the following code sample from Javascript:

let counter = 0;

const add = (x, y) => {
  return x+y;

add(2, 4); // 6
example of a function with side-effects

Here, the function add does something more than just calculating a value and returning it -- counter++. This is what's called a side effect.

When a function relies on or modifies something outside of it's arguments, it's said to have side-effects.

This means that, naturally, all I/O is a side effect, since you're relying on or modifying the filesystem or a network instead of simply taking in values and returning a value, which can possibly impact the return value of the function between multiple runs.

Think of pure functions as mathematical functions that map values from one set to another; mapping is all they do. If you have a function f(x) = x * 2, the result of the function will always be the same, no matter how many times you evaluate it, and there's nothing 'modifiable' in the sense of mathematical functions.

The benefit of having pure functions is that you don't have to think twice before calling a function. You know that calling a function will only 'transform' one value into another, and you don't have to worry about it internally changing some state or performing some actions that you didn't know or forgot about.


Haskell does not evaluate expressions until it needs to. For example, if you have some code like

function multiply(x, y) {
    const unnecessary = (x*y) * (x*y);
    return x*y;

In a language that's not lazily-evaluated, the linter will give you some kind of error like unused variable "unnecessary", and if you don't remove this line, you'll incur some performance penalty because this value will always be computed, even though it's not used (Though compilers can be smart enough to remove such computations). This won't happen in Haskell because values are only constructed when they're needed.

Laziness allows you to create infinite lists, which can be helpful for example when you need the first 50 positive integers – you can just create an infinite list and take the first 50 elements, and the rest will not be created simply because they're not needed.

Statically Typed

I'm sure you know what this means, but you probably haven't seen algebraic data types before, so have a look – it's pretty self explanatory.

data Tree = Node Int | Branch Tree Tree

let tree = Branch (Node 3) (Branch (Node 6) (Node 8))

       /  \
      3   /\
      	 /  \
        6    8
example of a tree like algebraic data type

Tree here is a type that can either be a Node or a Branch – this is what an algebraic data type is. They are called so because they are types formed by algebraic operations. In our example, the type Tree is formed by the operation '+' which denotes 'or' between the type constructors Node and Branch, and the type Branch is formed by a '*' or 'combination' of Tree and Tree.

Note that Node and Branch here are data constructors. They take arguments and construct a value of type Tree. Note that we ask for an Int to construct a Node. If we were to generalize this, we could do something like

data Tree a = Node a | Branch (Tree a) (Tree a)

Here, Tree is a type constructor, you give it a type (not value!) argument and it constructs a type (not value!). Type constructors construct types, data constructors construct values. For example, Tree Int will construct the type Node Int | Branch Tree Tree.

The data keyword is used to declare algebraic data types. The keyword type can be used to create a type synonym this way:

type Money = Int;

The compiler won't warn you when you try to pass an Int to a function that takes Money. It's just a synonym and nothing more.

Algebraic Data Types (ADTs) are widely used in Haskell.

Garbage Collected

The concept of GC in Haskell is the same as in other languages. This link might be helpful if you're wondering why Haskell needs a Garbage Collector, and you can check this link out for more information about Haskell's memory management model.

Starting out with some code

Let's have a look at some basics. Here's a sample program:

module Main where

main :: IO ()
main = do
  putStrLn "Enter your name"
  name <- getLine
  putStrLn "Enter your age"
  age <- getLine
  let goodbye = "Nice to meet you"
  putStrLn $ "Hello, " <> name <> " of age " <> age <> "!"
  putStrLn goodbye

-- this is a single line comment btw
{- and this is a multi line comment  -}

    Enter your name
    Enter your age
    Hello, jim of age 4!
    Nice to meet you
a haskell program that asks for name and age and prints it
  • Haskell code is written inside modules. You can import other modules after the first line to use stuff defined inside them. In the end, all the modules that you used are compiled together, and the main function in the Main module is run.
  • In most languages, you would call a function like functionName(param1, param2); but in Haskell, you don't need parantheses. The same call would look like functionName param1 param2.
  • The () here is an empty tuple. If you know a bit of Python, you already know what Tuples are. They're a kind of list that can have elements of different data types. Another example of a tuple is ("Hi", 5, 7) :: (String, Int, Int).
  • The $ you see in the second last line is just a neat little way to avoid brackets. Here, if we don't use the $, it could be interpreted as
(putStrLn "Hello, ") <> name <> " of age " <> age <> "!"

But this isn't what we want, we want to combine all the strings and pass those as an argument. You could also do this like

putStrLn ("Hello, " <> name <> " of age " <> age <> "!")
  • The main :: IO () is the type signature for the function main. For example,
sum :: Int -> Int -> Int
sum x y = x + y

Int -> Int -> Int is read as "takes two parameters of type Int and returns a value of type Int".

This is a good time to mention that functions in Haskell are curried. What this means is that it's possible to partially apply functions. In many other languages if you pass fewer arguments than expected to a function, you'll get an error. In Haskell however, this is not the case. Suppose we have the function above, i.e sum which takes two arguments x and y of type Int, adds them, and returns the value. Let's say we had to add 2 with 4, 6, and 8. We could do something like this

module Main where

sum :: Int -> Int -> Int
sum x y = x + y

main :: IO ()
main = do
	putStrLn $ sum 2 4
	putStrLn $ sum 2 6
	putStrLn $ sum 2 8

There's a shorter way of doing this utilising currying:

module Main where

sum :: Int -> Int -> Int
sum x y = x + y

main :: IO ()
main = do
	let sumWith2 = sum 2
	putStrLn $ sumWith2 4
	putStrLn $ sumWith2 6
	putStrLn $ sumWith2 8
curried function example

Cool, right?! Let's re-arrange the type signature of the sum function.

sum :: Int -> (Int -> Int)

Doesn't this look like a function that takes an Int and returns a function that takes an Int and returns an Int? It does, and that's what it is, though the brackets didn't do anything. Every function in Haskell is curried by default. This means that if you don't provide sufficient arguments, you'll get back a function that takes the remaining arguments and then returns the result. Our curried sum function in Javascript would look like:

const sum = x => y => x + y;

const sumWithTwo = sum(2);


Alright, let's get back to our sample program; I'll copy paste it here so you don't have to scroll.

module Main where

main :: IO ()
main = do
  putStrLn "Enter your name"
  name <- getLine
  putStrLn "Enter your age"
  age <- getLine
  let goodbye = "Nice to meet you"
  putStrLn $ "Hello, " <> name <> " of age " <> age <> "!"
  putStrLn goodbye

I think I've talked about most of this. What's left is:

  • What does IO here mean?
  • What does do do?
  • Why's the name 'variable' defined using arrows, like name <- getLine and goodbye using = like let goodbye = "Nice to meet you"?
  • What's <>? Is it the string concatenation 'operator'?

Let's try to change the value of the 'variable' goodbye.

main = do
    let goodbye = "Nice to meet you"
    goodbye = "Bye"
    putStrLn goodbye

Looks fine... right?

app/Main.hs:3:13: error:
    parse error on input ‘=’
    Perhaps you need a 'let' in a 'do' block?
    e.g. 'let x = 5' instead of 'x = 5'
3 |     goodbye = "Bye"
  |             ^

This is because goodbye isn't a variable, it's called a 'let-binding', or just 'binding'. Nothing in Haskell is mutable. Once you define something, you can not change it. It helps to think of these as bindings because a binding wouldn't strike you as something you can change. In fact,

main = do
    let name = "Udit"
    putStrLn name
-- is syntactic sugar for
main = let name = "Udit" in putStrLn name

-- is syntactic sugar for
main = (\name -> putStrLn name) name

(\name -> putStrLn name) is a lambda. It's the same as (name) => putStrLn(name); in Javascript. It does certainly take some time to get used to using a backslash here, but it helps if you think of it as a lambda icon with an amputated leg (I read this somewhere myself lel). So let bindings are just syntactic sugar for an immediate call to a lambda! In Javascript this would look like (name => console.log(name))("Udit").

Oh, also, X is syntactic sugar for Y means that your program is converted from X to Y before being fed into the compiler. This can be done for various reasons like if you don't want to add new features to the language core but still want to implement simpler ways to do things. It sure does help here because let-bindings are nice and easy to read, and in the end it does do the same job as a immediate lambda call.

So, a few questions still remain.

  • What does IO here mean?
  • What does do do?
  • Why's the name 'variable' defined using arrows, like name <- getLine?
  • What's <>? Is it the string concatenation 'operator'?

When I first started out, this is exactly where I got stuck. What was going on here, what was this magic? do felt like it executed a set of statements. IO felt like... it marked a function as an I/O user? The arrows confused me a lot. Finally, I realised what do is syntactic sugar for. There's sure a lot of syntactic sugar in this language! It's amazing how much the do notation abstracts away.

main = do
    putStrLn "Enter your name"
    name <- getLine
    putStrLn $ "Hello, " <> name <> "!"

-- is syntactic sugar for

main = putStrLn "Enter your name" >>
         getLine >>= \name ->
             putStrLn $ "Hello, " <> name <> "!"

So now, what is >> and >>=?

Well, the thing is, this goes deep. In just this small sample program, we're using Monads and Semigroups. When I was starting out, I knew these existed in the language, but I thought they were things I would learn in the future when I try to make more complicated programs, but the truth is that the simplest of programs in Haskell need you to know about these concepts.

To understand this program along with every other program in Haskell, we'll need an idea about what a Monad is, for which we need an idea of what a Functor and an Applicative is, because all Monads in Haskell are also Functors and Applicatives. And finally, a bit about semigroups and monoids since they're so common but still fairly easy to grasp. These sound like big words, but, practically speaking they're not so hard once you've spent some time with them. Theoretically speaking, I have no idea, so I'll let you theoretically speak to yourself instead.

Let's first have a look at what typeclasses are, so we can move ahead to the real deal. It's a fairly simple concept.


Typeclasses are somewhat like an interface. If you've used Typescript or any other language that uses interfaces, you know what they're like. The difference in Haskell is that you don't need to define the type-classes of a data-type when creating the type. If you know about traits in Rust, Haskell typeclasses are the same thing. In fact, Rust traits were inspired by Haskell typeclasses.

type Name = String

type Surname = String

data PersonName = PN Name Surname

class Splitter s where
  split :: s -> (String, String)

instance Splitter PersonName where
  split (PN n sn) = (n, sn)

main :: IO ()
main = do
  let kath = PN "Kath" "Sandwitch"
  let splitKath = split kath
  putStrLn $ fst splitKath

-- outputs Kath

Here, we define a typeclass called Splitter, which mandates that every type S that is an instance of Splitter must have an implementation of the function split, which takes a value of type S, and returns a tuple of type (String, String).

Then, we define a new algebraic data type PersonName. Note that here, PersonName is the type. PN is a data constructor (function) that takes a Name and a Surname, and constructs a value of type PersonName. PN has the type Name -> Surname -> PersonName. Name and Surname are type-aliases. They both mean String.

Then we tell Haskell that our type PersonName is an instance of the typeclass Splitter, and provide an implementation of split :: PersonName -> (String, String). Note that we can destructure the algebraic data type right in the argument and then use the destructured values in our implementation. Once we've gotten the first name n and surname sn, all we have to do is pack them up in a tuple and send them back.

Now split is just a normal function, but it has the type Splitter a => a -> (String, String). What Splitter a => is, is a constraint. It tells Haskell that the a that it'll see in the following type definition must be an instance of the typeclass Splitter. You'll get a compile error if you try to call split with, say, an Int, since Int is not an instance of Splitter.

By the way, Kath and Sandwitch are both names for a cat suggested by a friend. Imagine calling your kat Kath, or Sandwitch. Oops, sandwich.

One last thing before we dive deep – have a look at this

2 + 2

45 - 3

10 / 4

They're all valid in Haskell, and probably every programming language you've ever seen. There is, however, something different here – +, -, and / are all just functions. Yep, no special operators that are embedded in the language. Just regular functions just like we could define like (+) :: Int -> Int -> Int.

The reason they can be called that way is that in Haskell, all functions that only consist of symbols are by default infix. This means 3 + 2 is equivalent to (+) 3 2 (yes, you need to wrap the symbol in parantheses to call it in a non-infix way or define it). This applies not just to +, - and /, but every function defined in Haskell that consists of just symbols (a lot are).

Now, we saw that functions with names composed only of special characters are infix by default, but what if you wanted to use a regular function in the infix way? Take our sum function, for example. It has the type sum :: Int -> Int -> Int. We can wrap it in backticks to use it in the infix way – 5 `sum` 6.

Now that we know what typeclasses and infix functions are, let's start with semigroups and monoids, since they're easier to explain and you don't need to know about Functors, Applicatives or Monads to get an idea about them.


In mathematics, a semigroup is a set S with a binary operation (say •) that can be applied to any two values in the set S to get a resultant value also in the set S. The operation • must be associative, which means that a • (b • c) must be equal to (a • b) • c) ∀ a, b, c ∈ S.

Let's translate Math to Haskell – the docs define this as:

" A type a is a Semigroup if it provides an associative function (<>) that lets you combine any two values of type a into one. "

Which pretty much sums it up.

class Semigroup a where
  (<>) :: a -> a -> a 

This is the minimal definition of a semigroup. This means that every type that is an instance of Semigroup must provide a function <> that takes two values of that type and combines them. Note that Haskell does not check the associativity of this operator in any way, so it's on the programmer to define this properly.

In our example program that asks someone for their name and age and prints it with a greeting, we used <> to concatenate strings, i.e "hi" <> "hello" = "hihello". This could be done because String is an instance of Semigroup. Another example of semigroups are lists. [1, 2] <> [3, 4] = [1, 2, 3, 4].


A Monoid is a semigroup with an identity value such that id <> val = val <> id = val. This is called mempty in Haskell. The (minimal) Monoid typeclass looks like:

class Semigroup a => Monoid a where
    mempty :: a

For example, for strings mempty would be the empty string "", since "" <> "hi" = "hi" <> "" = "hi". For lists, it would be the empty list [].

Good news - that's it! The next time you need to combine two values of the same type, check if that type has an instance of Semigroup. If it does, you don't need to think twice before using <>!

You might be wondering what the use of Monoids is, and why the identity value had to be defined; I'm not very clear on this as well, but I think it's so that mconcat could be implemented. mconcat x is just foldr (<>) mempty x. mconcat is a member of the typeclass Monoid, but it's created by default.

And finally it's time for functors, applicatives and monads. This might seem like unrelated theory, but it's very practical and used a lot in Haskell. I know it's hard to learn things you don't know the use for, but I can guarantee this isn't some "learn it now because you might need it later" kind of thing, but more of a "learn this now because you'll definitely use it a lot all the time you're writing Haskell" kind of thing. Things will make more sense when you make it to monads.

Before proceeding, let's define an algebraic data type: WR.

data WR a = WR a

Let's refer to values of type WR a as wrapped values, since after all they're just values of type a wrapped inside WR.

If you're wondering why we had to write WR once more on the right side, again, the WR on the left is called a type constructor, and constructs types. The WR on the right is a value constructor. So we could do

something :: WR Int
something = WR 4

Here, WR in the type definition constructs the type WR Int, while the WR in the definition constructs the value WR 4.


Now, let's say we have a value WR 5, and we want to double the value inside to make it WR 10. How do we go about doing that?

Speaking more generically, how do we apply some function f to a wrapped value v?

The answer (or one of the answers) is using a functor. Functor is a Haskell typeclass as such:

class Functor f where
	fmap :: (a -> b) -> f a -> f b

Note that a and b here are generic types. They could be anything. a = b is also possible. See how we use f a and f b in here? This means that f must be a type constructor that takes a type argument to create a type.

This says – for a type constructor f to be a functor, it must define it's behavior for a function fmap which takes as arguments:

1) A function that takes a value of type a and returns a value of type b

2) A value of type a wrapped inside f

and returns a value of type b wrapped inside f.

Sounds hard, but it's simple. Let's look at it simply then.

Let's go back to our example WR 5. We want to double the value inside to make it WR 10. Remember what the first argument to fmap was? (a -> b) – a function that takes a value of type a (which is Int in this case), and returns a value of type b, which we want here to be an Int, since doubling 5, an Int, will obviously also return an Int, which is 10.

Basically we need a function that takes an int and doubles it.

doubint = (*2)

Wait, what?

This is a point-free function. It's equivalent to doubint x = x * 2.

Point free programming refers to defining functions without having to name arguments. Arguments in this context are called points because they are the value of that argument at some point. So how does this work?

Remember when I said * is just a function that takes two arguments? And that every function in Haskell is curried by default? This means that when we write (*2), we're really just constructing a partially applied * function. It has the first argument set to 2, and now has the type Int -> Int (well not exactly, but it'll do for now).

Some people call this pointless programming, and I can see why, since can be less readable. It is, however, perfect for occasions when it is readable.

However, it also is perfect for one more occassion - when you want to flex on someone! :D

So let's move ahead with our shiny pointless – I mean, point free, function. We now need a wrapped value. We've had this one since the start – it's WR 5. So it's time to call fmap!

fmap doubint (WR 5) -- outputs WR 10

We did it! Though I've skipped over 2 things that I'll talk about at the end. For now, we're going a little slow on the Haskell side to allow you to have an idea of these concepts. You can try guessing what they are right now.

Note that Haskell allows you to use <$> instead of fmap all the time. Since <$> is only composed of special characters, it's by default infix. This shortens our previous expression down to doubint <$> WR 5.

I'm sure you've heard of the map function before. For example, in Javascript, you could do [1, 2, 3].map(x => x + 1); // [2, 3, 4]. It just applies the given function to every item of the list. Doesn't this sound familiar? If you think of a list as a wrapped value, this is exactly what fmap or <$> is. It takes a function that takes a value and returns a value, and applies it to the wrapped value.

Thinking this way, we can say (*2) <$> WR 5 is similar to [5].map(x => x*2)!

This is the same in Haskell as well, with lists this time. Lists are functors, and fmap on lists is just the list map – (+1) <$> [1, 2, 3] will be [2, 3, 4].

I've often heard the phrase a functor is anything that can be mapped over, but when I heard mapped over I could only think of the list map. This created more questions than answers for me, but I hope the explanation above could be of help. This will be more clear later, when we cover the 2 remaining things I mentioned earlier.


Now that you know what functors are, it shouldn't be all that hard to learn about applicatives. Applicatives do what Functors do, only that they take it one step further.

Functors apply a function to a wrapped value, while applicatives apply a wrapped function to a wrapped value.

This means that if we have a function

wrappedMultiply :: WR (Int -> Int -> Int)
wrappedMultiply = WR (*2)

and a value WR 5, and we want to apply the function wrappedMultiply to the value WR 5, we'll need the power of applicatives.

Note that (Int -> Int -> Int) is just another type, which means it fits in the generic a.

Applicative is a Haskell typeclass as such (minimal version):

class Functor f => Applicative f where
    pure :: a -> f a
    <*> :: f (a -> b) -> f a -> f b 

Here, Functor is a superclass of Applicative. This means that every Applicative in Haskell must also be a Functor.

Note that the only difference between <$> and <*> is that (a -> b) is now f (a -> b)!

We also have a function called pure here, which does a very simple job – It takes a value and wraps it in f. For example, pure 5 = WR 5, pure "hi" = WR "hi", and so on.

Let's try it out: WR (*2) <*> WR 5 -- outputs WR 10.

That's it! I'll talk about usages of functors and applicatives later because I feel like we should now move on to monads since it's the key to understanding almost every program, and we've gotten through the important parts of the theory behind the concept of a functor and an applicative.


This is a topic that's talked about fairly often as one of the hardest concepts to grasp, and I probably have no idea about it theoretically, but I believe it's practically easy to understand. By this I mean it's not very hard to learn about and understand enough about monads to be able to write Haskell programs.

As everything else, Monad is a typeclass as such (minimal version):

class Applicative m => Monad m where
    (>>=) :: m a -> (a -> m b) -> m b

What I mean by a minimal definition is that I've mentioned only the members of the typeclass that must be manually defined to make an instance of the typeclass in question. Other members exist, but would have a default implementation.

The Monad typeclass also consists of return, which is supposed to do the same thing that pure from the applicative world does. Many tutorials and websites use return, but the Haskell wiki recommends that return and pure should have the same implementation, and I believe pure is better because it doesn't restrict you to Monads, and prevents confusion, since return is used to return values in other imperative languages.

Note that the return function here is not used to return values. Just like pure 5 would be WR 5 for our ADT, return 5 would also be WR 5. Another benefit is that pure is shorter and looks cooler than return.

Also note that Applicative is a superclass of Monad, and Functor is a superclass of Applicative. This makes the chain Functor -> Applicative -> Monad, and hence it was important to understand the concept behind Functors and Applicatives first.

Now, let's talk about a very important function which you'll see every minute of doing Haskell either implicitly using do or explicitly: >>=, which is also called bind or monadic bind.

The bind function has the type (>>=): m a -> (a ->  m b) -> m b. This means it takes as arguments:

  • a wrapped value of type a.
  • a function that takes a value of type a (not a wrapped value!) and returns a wrapped value of type b (which is then returned)

and returns a value of type m b.

Essentially, bind allows you to use the wrapped value. For example, if you had WR 5 and wanted to perform a set of operations on the value 5, you would like to get it as a value first. Bind will let you do this.

It takes a function that takes a value and returns a value wrapped in the same Monad. This means you cannot do WR 5 >>= \value -> IO 4, since IO 4 is in the IO monad and not the WR monad.

main = WR 5 >>= \value -> value * 4

Note that this was also possible fmap and <*>. So why do we need >>=? The answer is chaining.

Let's create a simple program

module Main where

main :: IO ()
main = do
    putStrLn "Enter name"
    name <- getLine
    putStrLn $ "Hello " <> name

Now let's think about imperative programming for a moment. When coding in the imperative style, you would do something like this as well. You'll set up a control flow that'll look like:

  • First ask for a name
  • Then get the name from user
  • Then print "Hello name"

But Haskell is written in a declarative way. Or so you'll hear everywhere. When I saw a program like above, I was confused as heck, because it looks exactly like what you would do in an imperative programming language.

This is the power and beauty of Haskell – abstract notions and ideas hidden under a cozy and simple-looking structure made simple enough to allow people to utilise them for real work and not just academics.

Remember that the imperative way is just a set of instructions, and the computer is asked to execute them in order. "do this, then do this, then do this". They're not connected; they're just individual commands that're being executed in order.

The Haskell version might look like this too, but let's desugar it and see what it actually is (the do notation is just syntactic sugar, it's converted to another form before being passed to the compiler):

main =
    putStrLn "Enter name" >>
        getLine >>= \name ->
            putStrLn $ "Hello, " <> name
De-sugared example program

Yes, it's just monadic binds. We're only using functions – no "control flow" is being fed into the computer. No unrelated set of commands that we want the computer to "just execute in order". Just function calls and nothing else.

Whenever you see infix symbols, remember that these aren't some special syntax and are just regular functions being used in the infix way.

We see a new thing here: >>. This isn't anything new – it's just the monadic bind for when you don't care about the value inside. For example, something = WR 5 >> WR 10 will be WR 10. This is the same as something = WR 5 >>= \someValue -> WR 10; you aren't using someValue so might as well just use >> to not have to create binds without a reason.

I'll once again try putting into words the >>= function: it's a function that takes a value of type a wrapped in some Monad m and another function (say f') that takes a value of type a and returns a value of type b wrapped in the same monad m. What you get back from >>= is what you get from f'.

This allows us to chain expressions together and define the way a program is supposed to work.

You still might be confused about Monads, but I'm sure this'll go away once you try making some programs and practice a bit more.

Alright, we also left something very important for later: IO.

IO isn't very different from our type WR (it actually is, but this way of thinking will do for now).

To understand the concept behind IO, we first need to go back to when I said 'Haskell is a purely functional programming language'. In Haskell, every function is a pure function that has no side effects. A side effect is something that changes or accesses some global state which may result in the output of it being different for the same input among multiple runs.

I/O is also a side effect, since for example, consider you have a function that reads a file. Reading a file is I/O. This function takes an argument which is the file name, and returns the contents of the file. This function is not a pure function, since it's output for the same argument can change depending on the contents of that particular file in the filesystem.

Printing to screen and so on is also I/O and a side effect, since we return an empty tuple from, say, putStrLn, but we're printing to screen, which is a side effect since we're performing some extra action which we aren't returning.

So how can we say that Haskell functions are pure despite being able to do I/O? There's a little trick involved here. Every function that does some IO returns something wrapped in the IO monad (IO a). For example, putStrLn returns IO () and getLine returns IO String.

The idea is that when we call the function that prints something on the screen, it returns the state of the world we live in – every atom, every particle, the state of all matter after we've printed that thing onto the screen. When we print another string, that state is replaced with the state of the world after the second string has been printed to the screen. Remember that our main function returned a value wrapped in IO? That's right. main is a pure function because it returns the new state of the world with all the IO that we did back to the world.

Let's now have a look at the arrow binding syntax.

main :: IO ()
main = do
    let something = putStrLn "Hello"
    putStrLn "Bye"

This will just output "Bye". This is because something here is just a binding of type IO (). In order to actually evaluate this, you need to use <- this way:

main :: IO ()
main = do
    let something = putStrLn "Hello"
    val <- something
    putStrLn "Bye"

Now you'll see both "Hello" and "Bye" printed on the screen. Here, val will now have the type (). Note that it's no longer wrapped in IO! this is because it's actually an argument to the bind function, since do notation is syntactic sugar for a series of binds like we saw above.

Effects are only ran when the wrapped value is 'unpacked'. It could be unpacked using the monadic bind, or using <$> or <*>, otherwise you just have a wrapped value that you don't know anything about until you 'unpack' it.

You can only use the arrow if the function on the right side returns some value wrapped in the same monad, IO in our case. This means that every function called inside a do block must be in the same monad (as in must return a value wrapped in the same monad). You can define let bindings that can contain any value though. This is because let bindings aren't chained with >>= or >>, they're just a named set of expressions or calls. If you never use a let-in binding, it will never even be evaluated.

The Monad typeclass also consists of a function called join, which has the type m (m a) -> m a. This just "removes one level of the monadic structure" like the wiki says. For example, join $ WR (WR 5)) will be WR 5.

An interesting thing to note is that bind (>>=) can be derived from fmap and join. Left as an exercise, since this doesn't really matter as much practically, but is interesting to know about and not so hard.

I believe I've talked about enough to let you get started with Haskell. I said when I was talking about Functors and Applicatives that I've left some things. This was the actual implementation of <$> and <*> for our type WR. This isn't so hard, since it only is a wrapper for one generic value.

data WR a = WR a deriving (Show)

instance Functor WR where
  fmap f (WR v) = WR $ f v

instance Applicative WR where
  pure = WR
  (WR f) <*> (WR x) = WR $ f x

The two things I left out were

  • the implementation of fmap and <*>
  • the deriving (Show) part

I've talked about the first point before so I'll skip over it.

Here, deriving (Show) just tells Haskell to automatically derive the implementation of whatever is required to make WR an instance of the typeclass Show. Show is needed if you want to be able to print values onto the screen.

print is a function I haven't talked about before, but it's pretty simple. We've seen that putStrLn prints strings onto the screen. print prints anything that is an instance of Show onto the screen. As simple as that.

There's something else I would like to clarify that might sound confusing even though we've talked about it before: pure = WR. If you remember pure, it just lifts a value into a monad, for example pure 5 = WR 5 in the context of WR. WR here is the data constructor for WR of type a -> WR a. It hence makes perfect sense to do this.

Pattern Matching

Let's define our familiar tree ADT again:

data Tree a = Node a | Branch (Tree a) (Tree a) deriving (Show)

Now, what if we wanted to make this into a functor? Suppose we wanted to add all values in a tree. Let's define an Int tree.

       /  \__
      /\    /\
     3 	1  /  \
          6   /\
             /  \
            2    9

Holy shit making a tree with dashes and slashes is hard. Anyways, ignore that horizontal line, it's just a regular tree.

So, what if we wanted to add something to all the values of this tree? But not just this tree. Any Tree.

One idea is to use fmap. This certainly gives me the feel of "mapping over" the tree. Let's do it.

instance Functor Tree where
    fmap f (Node val) = Node $ f val
    fmap f (Branch t1 t2) = Branch (fmap f t1) (fmap f t2)

Now this is a pattern match. Note that we're defining the same function on different lines, with the difference being in the Tree value. Since Tree can be either of these, we can write it this way.

There's also a more verbose way to pattern match using case:

instance Functor Tree where
    fmap f x = case x of
        Node val -> Node $ f val
        Branch t1 t2 -> Branch (fmap f t1) (fmap f t2)

Also note that this is a recursive call. If we encounter a node, we just apply the function f to that node. If we encounter a Branch, we apply the function f to both branches. If one of those branches is a branch too, the same happens.

Let's try this out!

data Tree a = Node a | Branch (Tree a) (Tree a) deriving (Show)

instance Functor Tree where
    fmap f x = case x of
        Node val -> Node $ f val
        Branch t1 t2 -> Branch (fmap f t1) (fmap f t2)

ourTree :: Tree Int
ourTree = Branch (Branch (Node 3) (Node 1)) (Branch (Node 6) (Branch (Node 2) (Node 9)))

main :: IO ()
main = do
  print $ (+10) <$> ourTree

This outputs Branch (Branch (Node 13) (Node 11)) (Branch (Node 16) (Branch (Node 12) (Node 19))) – we did it!

I left the usage of Functors and Applicatives for later as well, so let's have a look at one of the more common usages that I've seen over the course of the previous few months. This specific usage involves the use of <$> and <*> to construct an ADT. This is common while streaming – you could be getting certain values needed to construct the ADT through the network or the fs, and this would help. Here I'll just use getLine to demonstrate.

type Name = String
type Surname = String
type Age = String

data Person = Person Name Surname Age deriving (Show)

main :: IO ()
main = do
  putStrLn "Example use of <$> and <*>"
  person <- Person <$> getLine <*> getLine <*> getLine
  print person

Let's run it:

Example use of <$> and <*>
> Udit
> Karode
> 20
Person "Udit" "Karode" "20"

It works! (I added the "> " to make it more clear that those were inputs).

We make use of currying here once again. By now it should be clear that currying is a very useful thing in Haskell. It gives us more flexibility and freedom.

So when you do Person <$> getLine, you get a value of type IO (String -> String -> Person). This is because initially Person had the type String -> String -> String -> Person, and we fmap'd it with IO String that we received it from getLine. This called Person with the String from the IO String we got from getLine, and put the result into IO.

Note that we now have a function wrapped in IO, and want to apply more IO values to it (IO Strings that we get from getLine). Hence, we need to use <*> from now onward.

We could do the same thing using do notation:

type Name = String
type Surname = String
type Age = String

data Person = Person Name Surname Age deriving (Show)

main :: IO ()
main = do
  putStrLn "Example use of <$> and <*>"
  name <- getLine
  surname <- getLine
  age <- getLine
  let person = Person name surname age
  print person

This is a more pointful way of writing the same program. It's longer, but it's fine. The functor-applicative way of doing the same thing, however, is more succint, and should be preferred.

I'll provide examples of two monads that are very widely used in Haskell for error handling: Maybe and Either.

Maybe a can be either Just a or Nothing. Either a b can be Left a or Right b. This is useful in situations where something can error. It's standard in Haskell to denote error messages (or any error value) using Left and successes using Right.

The wonderful thing about using Maybe and Either is that errors cascade.

Let's see an example to show what I mean.

checkKind :: String -> Either String String
checkKind "human" = Left "Humans are not allowed in the animal zoo!"
checkKind _ = Right "Welcome to the animal zoo!"

checkNumberOfLegs :: String -> Either String String
checkNumberOfLegs "0" = Left "Zero-legged creatures are not allowed."
checkNumberOfLegs _ = Right "Have fun!"

checkBoth :: String -> String -> Either String String
checkBoth kind legs = do
  kindMsg <- checkKind kind
  legsMsg <- checkNumberOfLegs legs
  Right $ "Verification complete! " <> kindMsg <> legsMsg

main :: IO ()
main = do
  putStrLn "What's the kind of your species?"
  species <- getLine
  putStrLn "What's the number of legs you have?"
  legs <- getLine
  case checkBoth species legs of
    Right msg -> putStrLn msg
    Left msg -> putStrLn msg

_ here is just some value we don't care about.

We've used a pattern match on String that checks for "", and any other String _ in checkKind and checkNumberOfLegs.

What do you think will happen if we say kind is human and legs are non-zero?

What's the kind of your species?
> human 
What's the number of legs you have?
> 2
Humans are not allowed in the animal zoo!

The number of legs were never even checked. This is because Nothing >> Just something = Nothing, and Left something >> Right something = Left something.

This means that if one expression in the do block evaluates to Nothing or Left, every expression from then onwards becomes Nothing or Left upto the return value. This allows to have a huge stack of function calls where we can simply and easily propogate errors throughout till the point where we use those functions on the outer-most layer. It's like a break or early return in imperative languages but way more powerful and customisable.

I think that's more than enough to get you going with Haskell, but there's still much more to know about Haskell. For example, we have been chaining types using -> in type definitions, but did you know that this isn't some special magic syntax, but just a type constructor? It amazes me how simple Haskell can be at times.

I'll now recommend making some basic programs to get a greater idea of Haskell. You can then read the following to get a better idea of everything:

I'll borrow some words from "What I Wish I Knew When Learning Haskell":

As a programming language, Haskell pushes the frontiers of programming language design more so than any other general purpose language while still remaining practical for everyday use.
Beyond language features, Haskell remains an organic, community-driven effort, run by its userbase instead of by corporate influences. While there are some Haskell companies and consultancies, most are fairly small and none have an outsized influence on the development of the language. This is in stark contrast to ecosystems like Java and Go where Oracle and Google dominate all development. In fact, the Haskell community is a synthesis between multiple disciplines of academic computer science and industrial users from large and small firms, all of whom contribute back to the language ecosystem.

Until next time!