Skip to main content

What is functional programming

Functional Programming is programming with pure functions. Mathematical functions.

A quick search on the internet may lead you to the following definition:

A (pure) function is a procedure that given the same input always return the same output without any observable side-effect.

The term "side effect" does not yet have any specific meaning (we'll see in the future how to give a formal definition), what matters is to have some sort of intuition, think about opening a file or writing into a database.

For the time being we can limit ourselves to say that a side effect is anything a function does besides returning a value.

What is the structure of a program that uses exclusively pure functions?

A functional program tends to be written like a pipeline:

const program = pipe(
input,
f1, // pure function
f2, // pure function
f3, // pure function
...
)

What happens here is that input is passed to the first function f1, which returns a value that is passed to the second function f2, which returns a value that is passed as an argument to the third function f3, and so on.

Demo

00_pipe_and_flow.ts

We'll see how functional programming provides us with tools to structure our code in that style.

Other than understanding what functional programming is, it is also essential to understand what is it's goal.

Functional programming's goal is to tame a system's complexity through the use of formal models, and to give careful attention to code's properties and refactoring ease.

Functional programming will help teach people the mathematics behind program construction:

  • how to write composable code
  • how to reason about side effects
  • how to write consistent, general, less ad-hoc APIs

What does it mean to give careful attention to code's properties? Let's see with an example:

Example

Why can we say that the Array's map method is "more functional" than a for loop?

// input
const xs: Array<number> = [1, 2, 3]

// transformation
const double = (n: number): number => n * 2

// result: I want an array where each `xs`' element is doubled
const ys: Array<number> = []
for (let i = 0; i <= xs.length; i++) {
ys.push(double(xs[i]))
}

A for loop offers a lot of flexibility, I can modify:

  • the starting index, let i = 0
  • the looping condition, i < xs.length
  • the step change, i++.

This also implies that I may introduce errors and that I have no guarantees about the returned value.

Quiz. Is the for loop correct?

See the answer here

Let's rewrite the same exercise using map.

// input
const xs: Array<number> = [1, 2, 3]

// transformation
const double = (n: number): number => n * 2

// result: I want an array where each `xs`' element is doubled
const ys: Array<number> = xs.map(double)

We can note how map lacks the same flexibility of a for loop, but it offers us some guarantees:

  • all the elements of the input array will be processed
  • the resulting array will always have the same number of elements as the starting one

In functional programming, where there's an emphasis on code properties rather than implementation details, the map operation is interesting due to its limitations

Think about how easier it is to review a PR that involves map rather than a for loop.