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 let me answer a question you may be asking.

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. Type system
  3. Community

Syntax#

Functional programming languages often produce shorter programs than their imperative counterparts. Haskell sometimes takes 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 comprehend 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 Haskell’s syntax for expressing functions is very 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 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.

Think of the type-checks as safeguards which prevent us adding apples to oranges.

Type Inference#

Haskell has “type-inference”, meaning you do not have to add type information to 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 - the compiler 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 Haskell subreddit if that’s your thing.

On top of this there’s lots of books and YouTube tutorials too. As a result, finding help in the wild isn’t too difficult… there’s also some really good 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 exceptions.

Lazy#

  • Avoids repeated evaluations - results are saved and can be reused (see memoization).

  • Deferred evaluation - we only compute things when we absolutely need them.

  • 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.

Because Haskell is lazy, it supports infinite data structures. For example, in the snippet below we define an infinite list of integers, and then only take the first 5 elements:

let my_infinite_list = [1..]  -- an infinite list of integers starting at 1
take 5 my_infinite_list  -- returns [1, 2, 3, 4, 5]

Because evaluation is deferred, the infinite list my_infinite_list is only computed when we need to take 5. And even then, it only computes parts of the list that are needed.

Funky Functions#

Alright, now onto the good stuff.

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

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

Whatever comes after the last -> is the return type; everything before it represents the function’s input types. So foo above takes 2 String arguments and returns a Bool. The exception to this is a function which just returns a value, 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.

Tying everything together, we would define a function add which adds two integers together like 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. Below is the obligatory factorial function:

factorial :: Int -> Int
factorial x = if x == 0 || x == 1 then 1
              else x * factorial (x-1)

This is ok, but Haskell has more elegant ways to express this logic without the if ... then ... else.

One approach is to use guards which we won’t talk about here, but feel free to explore.

What we will talk about is pattern-matching, where you can specify which bits of a function should run when arguments have certain values.

Pattern Matching#

Taking our factorial function from above, we can redefine it using pattern matching:

factorial :: Int -> Int
factorial 0 = 1
factorial 1 = 1
factorial x = x * factorial (x-1)

You can see we have 2 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 x to cover every value that didn’t match the previous pattern(s), and we say “return x * the factorial of x - 1”.1

Calling Functions#

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

Haskell uses juxtaposition - you place the name of the function next to its arguments.

Saving our factorial function in a file called fac.hs and loading it into ghci:

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

We can get the factorial of 10 like so:

ghci> factorial 10
3628800

Notice how we juxtapose (place side-by-side) the function and its arguments.

Conclusion#

Congratulations! You’ve taken your first steps into the world of Haskell and functional programming. Let’s recap what we’ve covered:

  • We explored why Haskell stands out with its clean syntax, powerful type system, and vibrant community.

  • Pure and lazy evaluation: Haskell functions have no side effects and computations are deferred until absolutely necessary.

  • Function fundamentals: we covered how to define functions with type signatures, implement them with pattern matching, and call them using juxtaposition.

  • Recursion: you saw how Haskell embraces recursive solutions, from the classic factorial example to pattern matching techniques.

This introduction has only scratched the surface of what Haskell can do. The language’s type system alone could fill several tutorials, and we haven’t even touched on concepts like monads, functors, or Haskell’s approach to handling I/O in a pure functional context.

Ready to dive deeper? Here are some excellent resources to continue your Haskell journey:

Remember, functional programming can feel quite different if you’re coming from imperative languages. Don’t worry if recursion or immutability feel strange at first — with practice, the elegance of functional thinking will start to shine. Now fire up ghci and start experimenting - the best way to learn is to do.

To get you started, try writing a function that returns the length of a string, or one that reverses a list. Bonus: Can you write it recursively?


  1. Note that for both our factorial functions, we’re in trouble if passed an argument < 0. We could handle this with a conditional return, or using the Maybe type (which represents values that may or may not exist) for better type-safety. ↩︎