Hey there! Thinking about functional programming? Wonderful! I’m going to introduce you to the basics of a popular functional programming language called Haskell.

What’s Haskell? It’s a lazy and pure functional language. If those terms don’t make sense, don’t worry. All will be revealed.

Disclaimer: this isn’t a comprehensive Haskell tutorial. It’s a quick intro to get you up-to-speed on the basics, so you can follow other articles on this site or follow along more in-depth tutorials.

I will link to more in-depth material at the end.

Installing#

Before we start, I recommend checking out the Haskell website to get everything installed and set up.

To play with functions, the Haskell development tools include a handy top-level interpreter called ghci (The Glasgow Haskell Compiler Interactive). It’s a little command line where you can run functions, see the types of things, and much more! I recommend checking this out to get a feel for it.

Everything installed and set up? Splendid.

Before we get to the nitty-gritty, why do I think Haskell is a good choice of functional language?

Why Haskell?#

There are a lot of functional languages out there, but I think Haskell is a particularly good choice for the following reasons:

  1. Syntax
  2. The type system
  3. Community

Syntax#

Functional programming languages often produce shorter programs than their imperative counterparts.

Haskell can sometimes take this to the extreme by making use of symbols for common functions.

This is a double-edged sword; code can be incredibly short and concise, but it can be hard to read and understand for the less-experienced.

We’ll avoid heavy use of symbols in this article - where we can’t avoid them, I’ll explain what everything means.

Aside from that, I think the Haskell syntax for expressing functions is clean and readable, and since functional programming is all about functions that’s just what we want!

The Type System#

The Haskell type system is vast, incredibly expressive, and mostly out of scope for this article. But there are two important features I think make Haskell is an amazing language.

Static Typing#

Haskell is a statically typed programming language, meaning programs are type-checked by the compiler before the program is run.

What’s type-checking? In a sentence - don’t add apples to oranges. To be more specific, Haskell has a sophisticated type system with lots of rules. The compiler will check that you haven’t broken these rules while compiling your program. For example, the compiler will check that you haven’t tried to add an Int to a String.

Type Inference#

Haskell has “type-inference”, meaning you don’t have to add type information to things in your program as the compiler is clever-enough to work out the types of things.

This is handy for keeping your code clear, but in some cases adding type information can aid clarity. If you want to add type annotations that’s fine, but Haskell will still check that your annotations are correct.

Development Community#

Haskell has a very active community. There are lots of Haskell repos on Git(Hub|Lab) and lots of open-source projects. Also there’s an active subreddit if that’s more your thing.

On top of this there’s lots of books and YouTube tutorials too! This means finding help in the wild isn’t too difficult… there’s also really cool blogs out there 👀.

Alright, let’s start.

Purely Lazy#

Haskell is a pure and lazy programming language, but what does that mean? Well let’s think about them for a moment.

Pure#

  • No side effects - functions take input(s), do some work, then deliver outputs. They never “change the world”: there’s no I/O, no variables, no exceptions - nothing!
  • Expressions replaceable by values - because functions are pure, calling a function with the same arguments will always give the same result. We calculate the value once, save it, and any time we see the same function call in our program, we can replace it with our saved value - a neat optimisation.
  • Immutable data - variables and data structures can’t change once defined. If
    x = "Hello"
    
    then x will be "Hello" forever, no if’s, and’s or but’s.

Lazy#

  • Deferred evaluation - we only compute add 5 3 when we absolutely need it.
  • Avoids repeated evaluations - results are saved and reused later if needed (see memoization).
  • No strict order - since things aren’t computed until needed there’s no fixed order, as we might need different things at different times. This opens the door for more cool compiler optimisations.

Funky Functions#

Haskell is a functional programming language, so programs are defined in terms of functions.

To define a function, we specify it’s name, followed by ::, then it’s type signature.

Functions may take no arguments and just return a value (for example the function bar :: Char always returns a Char), or they can take multiple arguments, such as foo :: String -> String -> Bool.

Whatever is to the right of the last -> is what the function returns, everything else are arguments. So foo takes 2 String arguments and returns a Bool. The exception to this is a function which just returns a value (taking no arguments) such as bar :: Char.

After the type signature, we define the function body.

The function body starts with the name of the function, then the argument list, then =, then the actual logic.

Putting everything together, we could define a function called add which adds two integers together as so:

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

add takes two Int arguments (the first two Int -> Int) and returns an Int answer. We label the two Int arguments x and y.

The add function is equivalent to the following C code:

int add(int x, int y) {
    return x + y;
}

Recursion#

Haskell makes heavy use of recursion so you’ll often see functions defined recursively.

Haskell also has a feature called pattern-matching, where you can specify which bits of a function should run when given certain arguments.

Pattern Matching#

To define a recursive function using patter-matching, each pattern is given it’s own line.

So to define a function sum' n1 which sums the integers from 1 to n:

sum' :: Int -> Int
sum' 0 = 0
sum' 1 = 1
sum' n = n + sum' (n - 1)

You can see we have base-cases when the argument is 0 or 1. This is when we stop recursing and actually return something.

We also have a recursive case. We call the argument n to cover every value that didn’t match the previous 2 patterns (0 and 1), and we say “return n + the sum' of n - 1”.1

Calling Functions#

Calling functions in Haskell is a little different to other languages.

Haskell uses juxtaposition. Saving our sum' function into a file called sum.hs and loading it into ghci:

ghci> :l sum.hs
[1 of 2] Compiling Main             ( sum.hs, interpreted )
Ok, one module loaded.

We can get the sum from 1 to 10 like so:

ghci> sum' 10
54

Conclusion#

Phew! That’s a lot to take in. Lets take a moment to recap:

  • Haskell is a pure and lazy language (as if you didn’t know):
    • Purity = no internal state - functions take input and return output.
    • Lazy means values are only computed when needed, with no guaranteed evaluation order - 5 + 3 may become 3 + 5.

  1. We hace to call the function sum' so that our function doesn’t collide with the built-in sum function. ↩︎ ↩︎