Thinking in Expressions

What Are Expressions?

Imperative programs are built from statements, and read top to bottom.

let numbers = [1, 2, 3];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
  sum += numbers[i];
}

Functional programs are composed of expressions, and read inside out.

Here’s the same program written in a functional style:

[1, 2, 3].reduce((a, b) => a + b, 0)

These expressions are atomic:

1 2 3 a b 0

We compose bigger expressions from smaller ones:

  • 1 2 3 -> [1, 2, 3]
  • a b -> a + b

Zoom out a little more:

  • [1, 2, 3] -> [1, 2, 3].reduce
  • a + b -> (a, b) => a + b

Zoom out all the way:

[1, 2, 3].reduce((a, b) => a + b, 0)

💡 In functional programming, every value is an expression, including the program itself.

Why Expressions?

Expressions are simpler to reason about

FP offers a simpler way of looking at the world:

  • Every value is an expression
    • Strings, numbers, and booleans are expressions
    • Arrays and objects are expressions
    • Functions are expressions
  • Any expression can be replaced with its simplest value without changing the program:

    • 1 + 2 can be replaced by 3 and vice versa
    • 5 * 3 can be replaced by 15 and vice versa
    • Math.sqrt(16) can be replaced by 4 and vice versa
    • [1, 2, 3].reduce((a, b) => a + b, 0) can be replaced by 6 and vice versa

Expressions are simpler to refactor

  1. Find the expression you want
  2. Draw a box around it
  3. Move it around anywhere inside its parent “box”

For example, let’s extract [1, 2, 3] into a variable:

let numbers = [1, 2, 3]
numbers.reduce((a, b) => a + b, 0)

Extract (a, b) => a + b into a variable:

let numbers = [1, 2, 3]
let add = (a, b) => a + b
numbers.reduce(add, 0)

Wrap it into a reusable function:

let add = (a, b) => a + b
let sum = (numbers) => numbers.reduce(add, 0)
sum([1, 2, 3])

Summary

  • Every value is an expression
  • Expressions can be replaced by their simplest values
  • Expressions are simpler to refactor and reason about