Functional Programming

Dear Computer

Chapter 6: Expressions

Functional Programming

A functional programming language hides the von Neumann architecture of the computer. Programming is not writing commands to update memory cells. Rather it is declaring axioms, which are foundational statements that do not need to be proven, and relations between data. These axioms and relations do not change over time; they are mathematical truths.

This view of programming is a significant departure from the view we adopt when writing C++ and Java. In imperative languages, variables may vary. In Haskell, they do not. We don't set i to 0. We recognize that i is 0 and will always be 0. That means we can't write a for loop that changes i, or any kind of loop for that matter, because loops require mutability. This probably sounds like extreme asceticism, but Haskell provides alternative mechanisms for iterating that do not compromise immutability.

Since mutation is outlawed, we cannot write a subprogram that produces a side-effect. For example, in Java, we can write this method to prepend an element at the front of a list:

Java
void prepend(int newItem, ArrayList<Integer> items) {
  items.add(0, newItem);
}

This method doesn't return anything. Instead, its work of mutating the list is a side-effect. There no void functions in Haskell, no functions that produce side effects. However, we can rewrite this method in a functional style in Java by having it build and return a new list instead of mutating the original:

Java
ImmutableList<Integer> prepend(int newItem, ImmutableList<Integer> items) {
  return new ImmutableList(newItem, items);
}

You could write this version of the function in Haskell since it doesn't mutate the list. Note that ImmutableList class is only hypothetical; it does not exist in the Java standard library.

Why is immutability something to strive for? Consider a television tuned in to the big game. A mutable television is inherently unsafe. If we step out of the room momentarily, someone may change the channel out from under us. We have three options for keeping it safe:

Immutability means that data can be shared across threads without having to synchronize access. Each thread can freely and simultaneously read the shared data. The data can be cached for faster access without it never needing to be updated. Even if we don't adopt Haskell as one of our pet languages, we still benefit from applying the functional style that we learn by writing it. When we remove side effects, our code will be less dependent on volatile state and easier to test.

A function in an imperative language is a sequence of statements. Each statement mutates the bank of memory cells in order to set the stage for the next statement. Since mutation cannot happen in Haskell, functions are not a series of statements. Rather they are a single expression. The value of that single expression is the function's return value. Because every chunk of code yields a value, sometimes Haskell and other functional languages are described as expression-oriented languages.

Java programs are built out of objects passing messages to one another. Haskell programs are built of functions calling each other. Functions in Haskell are first-class citizens that can be passed around and returned as easily as conventional data. They can also be chained together to form complex calculations. Given their importance, Haskell provides several ways to define functions, which we will examine in a later chapter. For now, we focus on the expressions that form the body of a function.

← Haskell as a LensTypes →