Functional Programming
Haskell is a purely functional programming language. Functional programming is about programming with functions. "What's so special about that?", you say, "I do that all the time when coding in Python, Java, C, C++, Rust or Go." The thing is, Python, Java, C, C++, Rust or Go "functions" aren't really functions at all; they are procedures, sequences of steps to be followed to achieve a desired effect. They just happen to be called functions (or methods in object-oriented parlance).
In mathematics, a function is a mapping from values in some source set \(X\) to values in some target set \(Y\):
A set is simply a set of values. In programming, we call them types,1 so we can read this function \(f\) as mapping a value of type \(X\) to a value of type \(Y\). If \(X\) is the integer type and \(Y\) is the double-precision floating point type, then we would write the fact that \(f\) maps an integer value to a double-precision floating point value as follows in Haskell:
f :: Int -> Double
This is called a type signature. It tells us the type of the argument that
\(f\) expects—Int
—and the type of \(f\)'s return value—Double
.
Every meaningful program we write takes some input and computes from it some output. That's just one big function. It maps the input to the output. This right here is the one big difference between imperative and functional programming.
Imperative programming views our program as a sequence of steps to be executed. Functional programming views our program as a function that maps its input to its output.
At this point, these are just two different ways to look at the same thing. No matter which programming language you write a program in, the computer executes a particular sequence of steps when running the program (the imperative view) but the effect of running the program is also that it takes some input and produces from it some output (the functional view).
The reason why understanding this difference is important is that whether we look at a program as a sequence of steps to be executed or as a function that maps input values to output values permeates the entire programming language.
When programming in Python, C or Java, we may decompose our program into many smaller procedures to be executed one after another, possibly repeatedly when used in a loop, and each is decomposed into smaller procedures or primitive operations built into the language to be executed in sequence. It's sequences of steps throughout.
When programming in a functional language, such as Haskell, it's all about functions that map input values to output values, and we build our whole program, the one big function we care about, from lots of smaller ones by composing them. We never think about steps that need to happen, only about defining functions that associate values of some type with values of some other (or the same) type.
So functional programming is about specifying mappings from input values to output values. How do we define such mappings? Again, we take our cue from mathematics.
-
This is exactly the denotational classification of types discussed in class when we talk about type systems. ↩