Functional Programming is a philosophy and approach to programming that espouses functions as the primary elements of the program. It is considerably different from the “iterative programming” that most students are used to. Here are some of its key aspects:
While there are many functional programming languages out there, and in fact many “mainstream” languages include functional-programming elements, we will focus on a specific language called Haskell, in honor of the logician Haskell Brooks Curry who developed many of the early ideas behind functional programming.
Haskell differs from most other languages you may have seen in many ways:
i
that keeps changing values. This is another feature that will take some getting used to and will be frustrating at times. But knowing that values cannot change can be very beneficial too once you get used to it, and it leads to safer programs.As an example of the differences between Haskell-style functional programming and iterative style, let us consider a simple function, called sumUpTo
. sumUpTo
takes as input one integer, n
, and is supposed to return the sum of all the numbers from 1
to n
, with 0
if n < 1
.
In an iterative language like Python, we might do something like this:
# A function sumUpTo in Python
def sumUpTo(n):
total = 0
for i in range (1, n + 1):
total = total + i
return total
Or a C version would look like this:
// A function sumUpTo in C
int function sumUpTo(int n) {
int total = 0;
for (int i = 1; i <= n; i += 1) {
total = total + i;
}
return total;
}
In both cases the logic goes as follows:
total
value to 0.i
from 1
to n
.i
, add that number to the total
.total
This is a standard approach to iterative programming. We take steps, and on each step we instruct the computer to adjust the values of some variables.
Functional programming is very different. It is more declarative. There are two approaches to the problem. One would be as follows, essentially a recursive approach:
n < 1
is 0
.n
is the outcome of adding n
to the result of calling sum
on n-1
.-- A sumUpTo function in Haskell
sumUpTo n | n < 1 = 0
| otherwise = n + sumUpTo (n - 1)
This is the approach closest to iterative programming. We have effectively defined a recursive function: To sum the numbers up to n
, you simply sum the numbers up to n-1
, then add n
to that.
Before we move to other approaches though, notice one important feature of Haskell: Functions do not need parentheses around their arguments, unless the parentheses are needed to resolve ambiguities. So sumUpTo n
did not need any parentheses, but sumUpTo (n - 1)
needed them so that it is not mistaken for (sumUpTo n) - 1
.
In fact in Haskell multiple arguments to a function are simply written next to each other: Instead of f(x, y)
we would write f x y
.
Function call/definition in Haskell:
funcName arg1 arg2 arg3
There are other approaches to writing the sumUpTo
function. Another approach breaks the problem in two steps, and creates a helper function along the way:
n
, build the list of numbers from 1
to n
.sumList
.-- A sumUpTo function in Haskell
sumUpTo n = sumList [1..n]
where sumList [] = 0
sumList (x:xs) = x + sumList xs
You may think this is inefficient, as it has to create the list first, but remember that Haskell uses “lazy evaluation”: It only computes entries as it needs them, it never has to build the whole list at once.
Finally, a third approach uses a higher-order function called foldl
which goes through the elements in a list and uses a function to combine them two at a time:
-- A sumUpTo function in Haskell using foldl
sumUpTo n = foldl (+) 0 [1..n]
This is probably the hardest one to read, but it is also more idiomatic of Haskell. By the end of this course you will feel comfortable writing such functions. What this does is the following:
[1:n]
).foldl
function along with two other inputs:
0
, analogous to setting our total to 0 to begin with.(+)
which stands for the addition function.Quicksort is a popular sorting algorithm. It is a divide-and-conquer algorithm (you will learn more about them in CS225), which sorts the values in an array. It operates as follows:
Let’s take a look at how this may look in C code. It typically uses a separate partition
function. Don’t worry if this doesn’t make sense right away.
// Quicksort in C
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
int partition (int arr[], int low, int high) {
int pivot = arr[high]; // pivot
int i = (low - 1); // Index of smaller element
for (int j = low; j <= high- 1; j++) {
if (arr[j] <= pivot) { // Current element must go to left side
i++; // increment index of smaller element
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]); // Put pivot in its place
return (i + 1);
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
The Haskell approach is in essence the same, except it focuses on a more high-level description of the process: The result of performing quicksort on a list is the result of taking all elements less than its first element, followed by that first element, followed by all elements greater than the first element.
This is not the most efficient implementation of this algorithm in Haskell, but it is illustrative of the language’s expressiveness.
-- Quicksort in Haskell
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x]
And here is an implementation that is a bit closer to the C version:
-- Quicksort in Haskell with helper function
--
-- partition returns a pair of the values that are up to
-- the pivot and those that are above.
partition pivot [] = ([], [])
partition pivot (x:xs)
| x <= pivot = (x:less, more)
| otherwise = (less, x:more)
where (less, more) = partition pivot xs
qsort [] = []
qsort (pivot:rest) = qsort less ++ [pivot] ++ qsort more
where (less, more) = partition pivot rest
To start an interactive session with Haskell, type ghci
in the terminal. This will bring up the Haskell prompt Prelude>
. The Prelude
part there means that a standard library of functions is loaded.
In order to make writing these multiline functions possible, you must run the following each time you start a session:
:set +m
Start by going to the sumUpTo
functions we saw earlier and pasting them in, followed by pressing Return
for an extra blank line. Then you can tests these functions by typing something like:
sumUpTo 10 --- Should be 55
sumUpTo (-3) --- Should be 0
After you have done this, use these functions as models for creating three versions of productUpTo
, which multiplies the numbers up to n
(with 1 being the default if n < 1
). So the following should work with these functions:
productUpTo 4 --- Should be 24
productUpTo (-3) --- Should be 1