Functional Programming in JavaScript

Alan Pearce

alan@alanpearce.eu

Table of Contents

  • 1. Why?
  • 2. Concepts
  • 3. Further concepts
  • 4. First-class functions
  • 5. Higher-order functions
  • 6. Higher-order functions (cont.)
  • 7. Pure functions
  • 8. Recursion
  • 9. Partial application
  • 10. Partial application (cont.)
  • 11. Currying
  • 12. Easy Currying
  • 13. Practical Currying
  • 14. Functional composition
  • 15. Functional composition (cont.)
  • 16. pipe
  • 17. Point-free programming
  • 18. Further Resources

1 Why?

Imperative programming is concerned with how

Functional programming is concerned with what

2 Concepts

  • First-class Functions
  • Higher-order Functions
  • Recursion
  • Pure functions
  • Currying & Partial Application

3 Further concepts

  • Lazy Evaluation
  • Types & Data Structures
  • Category Theory

4 First-class functions

  • Are values
  • Have no restriction on their use
  • Enable the use of callback functions in JavaScript

var fn = function () {
  return 2
}

5 Higher-order functions

Functions that operate on other functions are higher-order functions

var succ = function (x) {
  return x + 1
}

var arr = [1, 2, 3, 4]

arr.map(succ)

Here, Array.prototype.map is the higher-order function

6 Higher-order functions (cont.)

Functions that return functions are also higher-order functions

function adder (n) {
  return function (x) {
    return n + x
  }
}

var add1 = adder(1)

adder is a higher-order function

7 Pure functions

Functions without side-effects

var succ = (x) => x + 1

console.log(succ(succ(1)))

// could be optimised away by a compiler, e.g.:

console.log(3)

8 Recursion

Functions that call themselves

function fibonacci (n) {
  switch (n) {
    case 0:
    case 1:
      return 1
    default: 
      return fibonacci(n - 1) + fibonacci(n - 2)
  }
}

9 Partial application

The infamous Function.prototype.bind in JavaScript

function add (x, y) {
  return x + y
}

var add1 = add.bind(add, 1)

add1(3) // = 4

10 Partial application (cont.)

After ES6 introduced arrow functions, partial application has become more popular

var add = x => y => x + y

11 Currying

Related to partial application, but more implicit and general

Translates 1 function of arity n to n functions of arity 1

function volume (w, d, h) {
  return w * d * h
}

var vol = curry(volume)
vol(10)(20)(30)
// is strictly equivalent to
volume(10, 20, 30)

12 Easy Currying

In order to make currying (and partial application) easier to use, move the most important argument to a function to the end:

var badMap = (arr, fn) => arr.map(fn)
var goodMap = (fn, arr) => arr.map(fn)
var curriedBadMap = curry(badmap)
var curriedGoodMap = curry(goodMap)

var goodDoubleArray = goodMap(x => x * 2)
var badDoubleArray = badMap(_, x => x * 2)

The bad version requires the curry function to support a magic placeholder argument and doesn't look as clean.

13 Practical Currying

Currying is not automatic in JavaScript, as in other languages

External tools aren't (so far) able to statically analyse curried functions

Solution: Don't expose curried functions Instead, write functions as if currying were automatic

If consumers want to curry, they can. If they don't, their editor or language server will show them the arguments

14 Functional composition

Creating functions from other functions

Usually provided by compose (right-to-left) and pipe (left-to-right)

A very simple definition of compose for only two functions would look like this

function compose (f, g) {
  return function (...args) {
    return f(g(...args))
  }
}

15 Functional composition (cont.)

var plusOne = x => x + 1
var timesTwo = x => x * 2

var plusOneTimesTwo = compose(timesTwo, plusOne)
var timesTwoPlusOne = compose(plusOne, timesTwo)

nextDoubled(3) // = (3 + 1) * 2 = 8
doubledPlusOne(3) // = (3 * 2) + 1 = 7

16 pipe

What about pipe?

pipe does the same thing, but runs the functions the other way around

pipe(f, g) is the same as compose(g, f)

17 Point-free programming

With currying and higher-order functions, we (often) don't need to declare function arguments

var modulo = a => b => b % a
var eq = a => b => a === b

var isEven = x => eq(0)(modulo(2)(x))
var isEvenPointFree = compose(eq(0), modulo(2))

18 Further Resources