Recursive functions

Relevant content from Chapter 6 of Programming in Haskell 2nd Edition

Basic concepts

Many functions can naturally be defined in terms of other functions.

fac :: Int -> Int
fac n = product [1..n]

It is permissible to define functions in terms of themselves, in which case the function is called recursive.

fac :: Int -> Int
fac 0 = 1
fac n = n * fac (n - 1)

The first equation states that the factorial of zero is one, called a base case. Whereas the second equation states that the factorial of any other number is given by the product of that number and the factorial of its predecessor, called a recursive case.

fac 3
= { applying fac }
3 * fac 2
= { applying fac }
3 * (2 * fac 1)
= { applying fac }
3 * (2 * (1 * fac 0))
= { applying fac }
3 * (2 * (1 * 1))
= { applying * }
6

Note that the fac function does not loop forever, because each application decreases the integer argument by one, until it eventually reaches zero, at which point the recursion stops. Returning one as the factorial of zero is appropriate, because one is the identity for multiplication.

1 * x = x
x * 1 = x
-- For any integer x
(*) :: Int -> Int -> Int
m * 0 = 0
m * n = m + (m * (n - 1))


4 * 3
= { applying * }
4 + (4 * 2)
= { applying * }
4 + (4  + (4 * 1))
= { applying * }
4 + (4  + (4 + (4 * 0)))
= { applying * }
4 + (4  + (4 + 0))
= { applying + }
12

Recursion on lists

Recursion is not restricted to functions on integers, but can be used for lists as well.

prodcut :: Num a => [a] -> a
product [] = 1
product (n:ns) = n * product ns

The first equation states that the product of the empty lists of number is one. Whereas the second equation states that the product of a non-empty lists is given by multiplying the first number and the product of the remaining list.

product [2, 3, 4]
= { applying product }
2 * product [3, 4]
= { applying product }
2 * (3 * product [4])
= { applying product }
2 * (3 * (4 * product []))
= { applying product }
2 * (3 * (4 * 1))
= { applying * }
24

Recall that lists are actually constructed one element at a time using the cons operator.

length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

The length of the empty list is zero, and the length of any non-empty list is 1 in addition to the length of its tail. Note that the use of the wildcard pattern _ in the recursive case, which reflects the fact that calculating the length of a list does not depend upon the values of its elements.

reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x]

The reverse of the empty list is simply the empty list, and the reverse of a non-empty list is given by appending ++ the reverse of its tail and a singleton list compromising the head of the list.

reverse [1, 2, 3]
= { applying reverse }
reverse [2, 3] ++ [1]
= { applying reverse }
(reverse [3] ++ [2])++ [1]
= { applying reverse }
((reverse [] ++ [3]) ++ [2])++ [1]
= { applying reverse }
(([] ++ [3]) ++ [2])++ [1]
= { applying * }
[3, 2, 1]

The append operator ++ can itself be defined using recursion on its first argument.

(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs ++ ys)
[1, 2, 3] ++ [4, 5]
= { applying ++ }
1 : ([2, 3] ++ [4, 5])
= { applying ++ }
1 : (2 : ([3] ++ [4, 5]))
= { applying ++ }
1 : (2 : (3 : ([] ++ [4, 5])))
= { applying ++ }
1 : (2 : (3 : [4, 5]))
= { applying ++ }
[1, 2, 3, 4, 5]

The recursive definition for ++ formalizes the idea that two lists can be appended by copying elements from the first list until it is exhausted, at which point the second list is joined on at the end.

insert :: Ord a => a -> [a] -> [a]
insert x [] = [x]
inser x (y:ys) | x <= y = x : y : ys
               | otherwise = y : insert x ys

Inserting a new element into an empty list gives a singleton list, while for a non-empty list the result depends upon the ordering of the new element x and the head of the list y.

insert 3 [1, 2, 4, 5]
= { applying insert }
1 : insert 3 [2, 4, 5]
= { applying insert }
1 : (2 : (insert 3 [4, 5]))
= { applying insert }
1 : (2 : (3 : (4 : [5])))
= { list notation }
[1, 2, 3, 4, 5]

A function that implements insertion sort, in which the empty list is already sorted, and a non-empty list is sorted by inserting its head into the list that results from sorting its tail.

isort :: Ord a => [a] -> [a]
isort [] = []
isort (x:xs) = insert x (isort xs)
isort [3, 2, 1, 4]
= { applying isort }
insert 3 (isort [2, 1, 4])
= { applying isort }
insert 3 (insert 2 (isort [1, 4]))
= { applying isort }
insert 3 (insert 2 (insert 1 (isort [4])))
= { applying isort }
insert 3 (insert 2 (insert 1 (insert 4 (isort []))))
= { applying insert }
insert 3 (insert 2 (insert 1 (insert 4 [])))
= { applying insert }
insert 3 (insert 2 (insert 1 [4]))
= { applying insert }
insert 3 (insert 2 [1, 4])
= { applying insert }
insert 3 [1, 2, 4]
= { applying insert }
[1, 2, 3, 4]

Multiple arguments

Functions with multiple arguments can also be defined using recursion on more than one argument at the same time.

zip :: [a] -> [b] -> [(a, b)]
zip [] _ = []
zip _ [] = []
zip (x:xs) (y:ys) = (x, y) : zip xs ys
zip ['a', 'b', 'c'] [1, 2, 3, 4]
= { applying zip }
('a', 1) : (zip ['b', 'c'] [2, 3, 4])
= { applying zip }
('a', 1) : (('b', 2) : (zip ['c'] [3, 4]))
= { applying zip }
('a', 1) : (('b', 2) : (('c', 3) : (zip [] [4])))
= { applying zip }
('a', 1) : (('b', 2) : (('c', 3) : []))
= { list notation }
[('a', 1), ('b', 2), ('c', 3)]

Note that two base cases are required in the definition of zip, because either of the two argument lists may be empty.

drop :: Int ->  [a] -> [a]
drop 0 xs = xs
drop _  [] = []
drop n (_:xs) = drop (n - 1) xs

Two base cases are requires, one for removing zero elements, and one for attempting to remove elements from an empty list.

Multiple recursion

Functions can also be defined using multiple recursion, in which a function is applied more than once in its own definition.

fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n - 2) + fib (n - 1)
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
               where
                 smaller = [a | a <- xs, a <= x]
                 larger = [b | b <- xs, b > x]

The empty list is already sorted, and any non-empty list can be sorted by placing its head between two lists result from sorting those elements of its tails that are smaller and larger than the head.

Mutual recursion

Functions can also be defined using mutual recursion, in which two or more functions are all defined recursively in terms of each other.

even :: Int -> Bool
even 0 = True
even n = odd (n - 1)

odd :: Int -> Bool
odd 0 = False
odd n = even (n - 1)

Zero is even but not odd, and any other number is even if its predecessor is odd, and odd if its predecessor is even.

even 4
= { applying even }
odd 3
= { applying odd }
even 2
= { applying even }
odd 1
= { applying odd }
even 0
= { applying even }
True