An Introduction to Functional Programming

What will we cover today?

  • Functions as First-Class Citizens
  • Higher Order Functions
  • Purity
  • Currying & Partial Application
  • Composition

What is Functional Programming?

FP is...

A programming paradigm closely related to several mathematical fields such as category theory and lambda calculus (don't let that scare you!).

 

FP is a declarative style of programming, favoring expressions over statements. It is obsessed with functional purity and avoiding/properly handling side effects. In concert with this obsession, FP avoids changing state through mutations.

Functions as First Class Citizens

"Functions as first class citizens" refers to languages that allow functions to be assigned to variables or passed as arguments to functions.


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

  const result = add(add(2, 3), add(4, 5))

  console.log(result) // 14

Higher Order Functions

A higher order function does at least one of the following: accepts a function as an argument, or returns a new function.


  function emojiLogs (emoji) {
    return function (...strs) {
      console.log(`${emoji} ${[...strs].join(' ')}`)
    }
  }

  const catLog = emojiLog(😻)
  const dogLog = emojiLog(🐶)
  const logLog = emojiLog(🌲)

  catLog('Krios', 'Tali')
  // 😻 Krios Tali

  dogLog('<- Why do I not have one of these yet?!')
  // 🐶 <- Why do I not have one of these yet?!

  logLog('Douglas Firs are real nice!')
  // 🌲 Douglas Firs are real nice!

Krios & Tali

 

Any Mass Effect fans?

Purity

A pure function is one that, given the same inputs, will always return the same output without any side effects.


  // This is a pure function. Given the same inputs
  // we'll always get the same output and never change
  // the state outside the scope of our function
  function add (x, y) {
    return x + y
  }

  // Impure Example
  let nextId = 0

  function impureAdd (x, y) {
    nextId++ // This is a mutation and makes this function impure!
    return x + y
  }

"Yeah Kyle, but we do more complicated things than add numbers together!" You're totally right!


  // Impure data fetch function
  function impureGetData (url) {
    return fetch(url).then(response => response.json())
  }

  // Pure data fetch
  function getData (url) {
    return function () {
      return fetch(url).then(response => response.json())
    }
  }

But why does this make it pure? What do we gain?

Purity leads to easily testable functionality.

 

It also creates trustworthy contracts between our functions, a fact we will soon utilize.

Currying & Partial Application

This

Not This

Thai Red Curry

Currying is the technique of refactoring a function that normally accepts multiple arguments into one that accepts them one at a time.

 

A curried function will continue to return a unary function for each argument until the final one is provided. Then, the function is evaluated.

 

The arguments provided to a curried function are said to be "partially applied" while the function awaits its final argument.

Canonical Example


  // With function keywords
  function add (x) {
    return function (y) {
      return x + y
    }
  }

  // A more elegant way with arrow functions
  const elegantAdd = x => y => x + y

  // Let's use it
  const add2 = add(2) // 2 is partially applied, held in closure
  const add5 = add(5) // 5 is partially applied, held in closure
  const eight = add2(6) // receives 2nd argument, evals to 8

  console.log(add5(eight)) // 13

Let's take a common function and curry it.

Argument order is very important when currying functions. Generally, you want the most unstable argument to be passed last.


  const filter = fn => arr => arr.filter(fn)

  const lessThanTen = filter(x => x < 10)

  const arr = [1, 5, 20, 11, 3, 10]
  const arr2 = [3, 12, 14, 15, 2, 1]

  const filteredArr1 = lessThanTen(arr) // [1, 5, 3]
  const filteredArr2 = lessThanTen(arr2) // [3, 2, 1]

Composition

If you remember high school math, composition is the act of passing the result of one function to another function.

 

This takes the form of `f(g(x))` where `f` and `g` are both functions. But nesting functions can be really ugly.


  const scream = str => str.toUpperCase()
  const exclaim = str => `${str}!`
  const repeat = str => `${str} ${str}`

  const warnBob =
    repeat(exclaim(scream('Bob, the world is coming to an end')))

  console.log(warnBob)
  // BOB, THE WORLD IS COMING TO AN END! BOB, THE WORLD IS COMING TO AN END!

Instead of nesting functions, what if we used currying and partial application to create a function that would do the composition for us?


  // Pretend we imported `scream`, `exclaim`, and `repeat`
  // from the previous slide

  const compose = (...fns) => value =>
    fns.reduceRight((acc, fn) => fn(acc), value)

  const warnAdamantly = compose(
    repeat,
    exclaim,
    scream
  )

  const warnBob = warnAdamantly('Bob, the world is coming to an end')

  console.log(warnBob)
  // BOB, THE WORLD IS COMING TO AN END! BOB, THE WORLD IS COMING TO AN END!

Exercises

Resources

Intro to Functional Programming

By Kyle Shevlin

Intro to Functional Programming

Functional Programming in JavaScript is all the rage these days, but how do you get started? This intro to FP will give the building blocks to start using it in your applications right away.

  • 726