Getting Started with Haskell

Overview

In this chapter you’ll learn how to write and run simple Haskell programs. You’ll also learn about some of the most important pieces of Haskell’s syntax, and get some hands on experience with how Haskell differs from other languages you might have used.

Factorials

The factorial function is a simple function that you can define recursively. You can compute the factorial of a number, n, by multiplying all of the numbers up to n. For example:

factorial 5 = 5 * 4 * 3 * 2 * 1 = 120

Try implementing your own factorial function. You can test your implementation in ghci and compare its output to the example:

λ factorial 1
1
λ factorial 3
6
λ factorial 5
120
λ factorial 10
3628800
λ factorial 25
15511210043330985984000000

Hints

Click to reveal

Remember that a recursive function needs both a base case that tells the function to stop calling itself, and a recursive case where the function calls itself with a smaller value.

The base case for your factorial function is when the number you are calculating is less than, or equal to, one.

Click to reveal

In the recursive case of your function, you need to multiply the current number by the next smallest factorial.

Click to reveal

You can solve this problem using either if expressions or guards

Click to reveal

Imagine that we’d use parentheses in the factorial example. We might have written it like this:

factorial 5 = 5 * (4 * (3 * (2 * 1)))

Think about what function would represent the value inside of each set of parentheses.

Solution

Click to reveal

You can implement this function using either an if expression or a guard. We’ll look at both solutions, starting with the solution that uses an if expression.

The first thing we need to do is write our function and define our base case. We’ll leave the rest of the function undefined for the moment:

factorial n =
  if n <= 1
  then 1
  else undefined

Technically speaking, factorial is only defined for positive numbers. We haven’t yet learned how to prevent users from passing in negative numbers, so we’ll fall back to defensive programming practices and just check for any number less than or equal to one. If our number is small enough, we’ll return the smallest factorial number: one.

What about for a larger factorial? In that case we need to multiply the current number by the next smallest factorial. What’s the value of the next smallest factorial? We can find it out using the factorial function:

factorial n =
  if n <= 1
  then 1
  else n * factorial (n - 1)

As you can see in this example, the “next smallest factorial” is our recursive call to factorial with the next smallest value, n - 1.

If you prefer guards, you’ll notice that the implementation with them is nearly identical:

factorial n
  | n <= 1 = 1
  | otherwise = n * factorial (n - 1)

The Fibonacci Sequence

The fibonacci sequence is a sequence of numbers that can be defined recursively. The first 10 numbers of the fibonacci sequences are: 0,1,1,2,3,5,8,13,21,34. You can calculate any given fibonacci number, n, by adding up the two previous fibonacci numbers.

Write a function that will compute the nth fibonacci number for any given number, n. You can test your implementation in ghci and compare it to the example:

λ fibonacci 0
0
λ fibonacci 1
1
λ fibonacci 5
5
λ fibonacci 10
55
λ fibonacci 25
75025

Hints

Click to reveal

There are two cases where the example fibonacci function isn’t recursive:

  • The 0th fibonacci number is 0
  • The 1st fibonacci number is 1
Click to reveal

The recursive case of fibonacci needs to make two recursive calls, because it needs to add the next two smallest fibonacci values.

Solution

Click to reveal

You can implement the fibonacci function using either if expressions or guards. We’ll use guards in this solution.

The definition of fibonacci is similar to the factorial that you should have created in the previous exercise. Let’s take a look:

fibonacci n
  | n <= 0 = 0
  | n == 1 = 1
  | otherwise = fibonacci (n - 1) + fibonacci (n - 2)

Once again, you’ll notice that we’re being a bit generous with our smallest base case. We’re checking for numbers less than or equal to zero so that we can give a sensible answer even if the user passes in a negative number.

In the next base case, we check to see if our value is 1. If so, we just return 1. These two base cases let use get the sequence started with the first two numbers:

fibonacci 0 = 0
fibonacci 1 = 1

In the next step, n is 2. This is a recursive case, and we’re going to look back both one step to n = 1 and two steps to n = 0:

fibonacci 2 = fibonacci (2 - 1) + fibonacci (2 - 2)
            = fibonacci 1 + fibonacci 0
            = 1 + 1
            = 1

Let’s follow up one more time, just to make sure we have the pattern down.

fibonacci 3 = fibonacci (3 - 1) + fibonacci (3 - 2)
            = fibonacci 2 + fibonacci 1
            = (fibonacci (2 - 1) + fibonacci (2 - 2)) + 1
            = (fibonacci 1 + fibonacci 0) + 1
            = (1 + 0) + 1
            = 1 + 1
            = 2

Remember, when you’re working with recursive functions, try to walk through the recursion manually if you’re having trouble understanding how the function works.

Manual Currying

You’ve learned about how Haskell functions work by taking a single argument. One way to write a function that takes multiple arguments is to pass in a tuple of arguments. For example, consider this addition function:

uncurriedAddition nums =
  let
    a = fst nums
    b = snd nums
  in a + b

Haskell’s standard library includes two functions, curry and uncurry, that make it easy for you to convert between functions that take two arguments and functions that take a tuple. The curry function transforms a function like our uncurriedAddition function and turns it into one that takes two separate arguments. For example:

λ addition = curry uncurriedAddition
λ addOne = addition 1
λ addTwo = addition 2
λ addOne 1
2
λ addOne 2
3
λ addOne 3
4
λ addTwo 1
3
λ addTwo 2
4
λ addTwo 3
5

Similarly, the uncurry function takes a regular function with two arguments and converts it into a function that accepts a tuple. For example, using uncurry we could have rewritten uncurredAddition like this:

uncurriedAddition = uncurry (+)

Using what you’ve learned in this chapter, try implementing your own version of curry and uncurry.

Since the standard library already has functions named curry and uncurry, you should select different names for your implementations. After you’ve written your versions, compare the behavior to the standard library implementations to ensure that your versions behave the same way.

Hints

Click to reveal

Remember that functions can be passed around as ordinary arguments. For example, imagine that we have a function called addNumbers that adds two numbers:

addNumbers a b = a + b

Next, we could write a function callWithTwoArguments that takes a function, and the two arguments that we should call that function with, and returns the results:

callWithTwoArguments f a b = f a b

Finally, we can pass addNumbers to callWithTwoArguments like any other value. As an example:

addThreeAndFive = callWithTwoArguments addNumbers 3 5

As you’re working on this exercise, remember that you can pass the functions that you want to curry, or uncurry, just like you’d pass around any other argument.

Click to reveal

Remember that you can get the first element of a tuple using the fst function, and you can get the second element of a tuple using the snd function:

λ fst ("hello", "haskell")
"hello"

λ snd ("hello", "haskell")
"haskell"
Click to reveal

The built-in curry turns a function that takes a tuple into a function that takes two arguments. Let’s look at an example. Imagine that we have a function that adds two numbers from a tuple:

addTuple tuple = fst tuple + snd tuple

We can test this out in ghci and see that it works just like we’d expect.

λ addTuple (1,2)
3

If we use curry, we can call this like an ordinary function. We can either use curry and pass arguments all in one call:

λ curry addTuple 1 2
3

Or we can use curry to define a new version of the function that takes two non-tuple arguments:

λ addTwo = curry addTuple
λ addTwo 1 2
3
λ addTwo 3 4
7

The uncurry function works the same way, but it converts functions in the other direction. For example:

λ uncurry addTwo (1,2)
3
λ addTuple' = uncurry addTwo
λ addTuple' (2,3)
5

You can use these examples as you are testing your own implementation.

Solution

Click to reveal

Let’s start by defining our own version of curry called exampleCurry. Our function will need to take three arguments:

  • f is a function that takes in a tuple and returns a value
  • a is a value; it’s the first value in the tuple we’ll pass to f
  • b is a value; it’s the second value in the tuple we’ll pass to f

It’s easiest to write this function as a one-liner:

exampleCurry f a b = f (a,b)

In this code, we’re taking three arguments as input. We’re calling our first argument, the function we want to curry, using a tuple made up of the next two arguments.

If we use this function in ghci we can see it behaves like you’d expect:

λ addTuple tuple = fst tuple + snd tuple
λ exampleCurry addTuple 1 2
3

Just like with the curry function defined for us in Prelude, we can use exampleCurry to create a new function:

λ addTwo = exampleCurry addTuple
λ addTwo 1 2
3
λ addTwo 2 3
5

This might be a bit surprising, since you’re still getting used to Haskell. Remember that Haskell makes it easy for us to do partial application. We could have written addTwo without partial application by taking in the two arguments that we should call the curried function with:

λ addTwo a b = exampleCurry addTuple a b
λ addTwo 1 2
3
λ addTwo 2 3
5