Functional Programming

Concepts

Pure Functions

At the heart of functional programming is the formal mathematics of describing logic: lambda calculus. Mathematicians like to describe programs as transformations of data which leads to the first concept —  pure functions. Pure functions are functions without side-effects.Pure functions depend only on the inputs of the function, and the output should be the exact same for the same input. Here's an example:

// pure function
const add10 = (a) => a + 10
// impure function due to external non-constants
let x = 10
const addx = (a) => a + x
// also impure due to side-effect
const setx = (v) => x = v

The impure function indirectly depends on x. If you were to change x, then addx would output a different value for the same inputs. This makes it hard to statically analyze and optimize programs at compile-time. But more pragmatically for JavaScript developers, pure functions bound the cognitive load of programming. When you're writing a pure function, you only need to concern yourself with the body of the function. You don't need to worry about externalises that could cause problems, like anything that could change x while when you're writing the addx function.

Function Composition

One nice thing about pure functions is that you cancompose them into new functions. One special operator used to describe programs in lambda calculus is compose . Compose takes two functions and “composes” them into a new function. Check it out:

const add1 = (a) => a + 1
const times2 = (a) => a * 2
const compose = (a, b) => (c) => a(b(c))
const add1OfTimes2 = compose(add1, times2)
add1OfTimes2(5) // => 11

The compose is analogous to the preposition “of”. Notice the order of the arguments and how they're evaluated: add one of times two — the second function is evaluated first. Compose is the opposite of perhaps a more intuitive function you might be familiar with from unix called pipe, which accepts an array of functions.

const pipe = (fns) => (x) => fns.reduce((v, f) => f(v), x)
const times2add1 = pipe([times2, add1])
times2add1(5) // => 11

With function composition, we can now build more complicated data transformations by joining together (composing) smaller functions. This article does a great job of showing you how function composition can help you process data in a clean and concise way.

Pragmatically speaking, composition is a better alternative to object oriented inheritance. Here's a contrived, but real-world example for you. Suppose you need to create a greeting for your users.

const greeting = (name) => `Hello ${name}`

Great! A simple, pure function. Then, your project manager says you now have some more data about your users and wants you to add prefixes to the names. So you go ahead and write this code:

const greeting = (name, male=false, female=false) =>
`Hello ${male ? ‘Mr. ‘ : female ? ‘Ms. ‘ : ‘'} ${name}`

This code isn't terrible, but what if we start adding more and more booleans for other categories such as “Dr.” or “Sir”? What if we add suffixes as well such as “MD” or “PhD”? And what if we want to have a casual greeting that says “Sup” instead of “Hello”? Well now things have really gotten out of hand.

Adding booleans like this to a function isn't exactly object oriented inheritance, but its a similar situation to when objects have properties and methods that get extended and overridden as they inherit. So as opposed to adding boolean options, lets try to use function composition:

const formalGreeting = (name) => `Hello ${name}`
const casualGreeting = (name) => `Sup ${name}`
const male = (name) => `Mr. ${name}`
const female = (name) => `Mrs. ${name}`
const doctor = (name) => `Dr. ${name}`
const phd = (name) => `${name} PhD`
const md = (name) => `${name} M.D.`

This is much more manageable and easier to reason about. Each function does a one simple thin g and we're able to compose them together easily. Now, we haven't handled all the cases here, and for that we can use our handy pipe function!

const identity = (x) => x
const greet = (name, options) => {
return pipe([
// greeting
options.formal ? formalGreeting :
casualGreeting,
// prefix
options.doctor ? doctor :
options.male ? male :
options.female ? female :
identity,
// suffix
options.phd ? phd :
options.md ?md :
identity
])(name)
}

Using pure functions and function composition simplifies error tracing. When an error is thrown, the stack trace shows through every function down to the source of the exception. Often, in an OOP stack-trace, you can't view the state of the object which led to the bug.

Function Currying

Function currying was invented by the same guy who invented Haskell — his name: Haskell Curry (correction: named after Haskell Curry). Function currying is when you call a function with fewer arguments than it wants and that function returns another function to accept the rest of the arguments. This is a good article that explains it in more detail, but here's a simple example using the Ramda.js curry function .

In the example below, we create a curried function “add”, which takes in two arguments. When we pass one argument, we get back a partially applied function we call “add1” which only takes one argument.

const add = R.curry((a, b) => a + b)
add(1, 2) // => 3
const add1 = add(1)
add1(2) // => 3
add1(10) // => 11

In Haskell, all functions are automatically curried. There are no optional or default arguments.

Pragmatically, function currying is really convenient when using functions with compose and pipe. For example:

const users = [{name: 'chet', age:25}, {name:'joe', age:24}]
R.pipe(
R.sortBy(R.prop('age')), // sort user by the age property
R.map(R.prop('name')), // get each name property
R.join(', '), // join the names with a comma
)(users)

This makes data processing feel very declarative.

Monads, Functors, and Fancy Words

Monads and functors read this article

Monads are pretty interesting though. Monads can be thought of as a container for a value, and to open up the container and do something to the value, you need to map over it. Here's a simple example:

		// monad
list = [-1,0,1]
list.map(inc) // => [0,1,2]
list.map(isZero) // => [false, true, false]

The important thing about monads and functors is that mathematicians have been researching these ideas in category theory . This provides us not only a framework for understanding programs, but algebraic theorems and proofs we can use to statically analyze and optimize our code when it's compiled. This is one of the main benefits of Haskell — the Glasgow Haskell Compiler is a feat of human ingenuity.

There are all kinds of theorems and identities expressed in category theory. For example, here's a simple identity:

list.map(inc).map(isZero) // => [true, false, false]
list.map(compose(isZero, inc)) // => [true, false, false]

When map is compiled, it uses an efficient while loop. In general this is a O(n) operation (linear time) , but there is still overhead associated with incrementing the pointer to the next item in the list. So the second version is actually twice as performant. These are the kind of transformations that Haskell does to your code at compile-time to make it fast — and there's a really cool trick to doing this that I'll explain later.

To expand on monads just a little, there's a very interesting monad called the Maybe monad (sometimes called Option or Optional in Swift). In Haskell, there's no such thing as null or undefined. To express something as being potentially null, you need to wrap it in a monad so the Haskell compiler knows what to do with it.

The Maybe monad is a union type that's either Nothing or Just something. In Haskell you'd define a Maybe like this:

type Maybe = Nothing | Just x

The lowercase x just means any other type.

Being a monad, you can .map() over a Maybe to change the value it contains! When you map over a Maybe, if it of type Just, then we apply the function to the value and returns a new Just with that new value. If a the Maybe is of type Nothing, then we return Nothing. In Haskell, the syntax is quite elegant and uses pattern matching, but in JavaScript you might use a Maybe like this:

const x = Maybe.Just(10)
const n = x.map(inc)
n.isJust() // true
n.value() // 11
const x= Maybe.Nothing
const n = x.map(inc) // no error!
n.isNothing // true

This monad may not seem terribly useful in your Javascript code, but its interesting to see why it's so useful in Haskell. Haskell requires you to define what to do in every edge-case of your program, otherwise it won't compile. When you make an HTTP request, you get back a Maybe type because the request may fail and return nothing. And if you didn't handle the case in which the request failed, then your program won't compile. This basically means that it's impossible to get runtime errors. Maybe your code does the wrong thing, but it doesn't just magically break like things tend to do in Javascript.

This is a big selling point for using Elm. The type system and compiler enforces that your program will run without runtime errors.

Thinking about code in the context of monads and algebraic structures will help you define and understand your problem in a structured way. For example, an interesting extension of Maybe is the Railway-Oriented Programming concept for error handling. And observable streams are monads as well for dealing with asynchronous events.

There are all kinds of fancy monads and many other words that I don't myself fully understand. But to keep all the lingo consistent, there are specifications like fantasy-land and the typeclassopedia which try to unify different concepts in category theory for the purpose of writing idiomatic functional code.

Referential Transparency and Immutability

Another implication of leveraging all this category theory and lambda calculus stuff is referential transparency . Its really hard for mathematicians to analyze logical programs when two things that are the same are not equal to each other. This is an issue all over the place in Javascript.

{} == {} // false
[] == [] // false
[1,2] == [1,2] // false

Now imagine having to do math in a world without referential transparency. You would not be able to write proofs that say that an empty array is the same things as an empty array. What should matter is only the value of the array, not the reference pointer to the array. And so functional programming languages resort to using deep-equals to compare values. But this isn't terribly performant, so there are some neat tricks to make this comparison quicker that leverages references.

Before moving on, I just want to make one thing clear: in functional programming, you cannot mutate a variable without changing its reference. Otherwise, the function performing the mutation would be impure! Thus, you can assure that if two variables are referentially equal, their values must be equal as well. And since we can't mutate variables in-place, then we have to copy those values into a new memory location every time we want to transform it. This is a huge performance loss and results in garbage thrashing . But the solution is using structural sharing (persistent data structures) .

A simple example of structural sharing is a linked list . Suppose you only keep a reference to the end of the list. When comparing two lists, you can first start by seeing if the ends are referentially equal. This is a nice shortcut because if they are equal, then you're done — the two lists are the same! Otherwise, you'll have to start iterating through the items in each list to see if their values are equal. To efficiently add a value to this list, rather than copying entire the list into a new set of memory, you can simply add a link to a new node and keep track of the reference at the new tip. Thus, we've structurally shared the previous data structure in a new data structure with a new reference and we've persisted the previous data structure as well.

The generalized data structure for doing these immutable data transformations is called a hash array mapped trie (HAMT). This is exactly what Immutable.js and Mori.js do. Both Clojurescript and Haskell have this built into the compiler, although I'm not sure it's implemented in Elm yet.

Using immutable data structures can give you performance gains, and help keep your sanity. React assumes props and state are always immutable so it can do an efficient check to see if the previous props and state are referentially equal to the next props and state before unnecessarily re-rendering. And in other circumstance, using immutable data simply helps to ensure that values aren't changing without you realizing it.

Lazy Evaluation

Lazy evaluation is a general term that covers concepts like thunks and generators . Lazy evaluation means: don't do something until you have to, be lazy and procrastinate as long as possible.

First, we need to understand how programs evaluate. Pretty much every language you're used to uses innermost reduction. Innermost reduction looks like this:

square(3 + 4)
square(7) // evaluated the innermost expression
7 * 7
49

This is a sane and reasonable way of evaluating programs. But now, let's consider outermost reduction:

square(3 + 4)
(3 + 4) * (3 + 4) // evaluated the outermost expression
7 * (3 + 4)
7 * 7
49

Outermost is clearly less efficient — we've had to compute 3 + 4 twice, so the program took 5 steps instead of 4. This is no good. But Haskell keeps a reference to each expression and shares these references as they're passed down to parent expressions through the outermost reduction. Thus, when 3 + 4 is evaluated the first time, the reference to this expression now points to the expression, 7. Thus we get to skip the duplicate step.

square(3 + 4)
(3 + 4) * (3 + 4) // evaluated the outermost expression
7 * 7 // both reduced at the same time due to reference sharing
49
Fundamentally, lazy evaluation is outermost evaluation with reference sharing.

Haskell does all this stuff under the hood for you, and what that means is you can define things like infinite lists. For example, you can recursively define an infinite list of ones as 1 joined with itself.

ones = 1 : ones

Suppose you have a function take(n, list) which takes the first n elements of a list. If we used innermost reduction, we'd recursively evaluate list forever, because it's infinite. But instead, with outermost reduction, we lazily evaluate ones for just as many ones as we need!

However, since JavaScript and most other programming languages use innermost reduction, the only way we can replicate these constructs is by treating arrays as functions. For example:

const makeOnes = () => {next: () => 1}
ones = makeOnes()
ones.next() // => 1
ones.next() // => 1

Now we've effectively created a lazily evaluated infinite list based on the same recursive definition. Lets create an infinite list of natural numbers:

const makeNumbers = () => {
let n = 0
return {next: () => {
n += 1
return n
}
}
numbers = makeNumbers()
numbers.next() // 1
numbers.next() // 2
numbers.next() // 3

In ES2015, there's actually a standard for this and they're called function generators .

function* numbers() {
let n = 0
while(true) {
n += 1
yield n
}
}