Just Enough Functional Programming to be a Danger to Yourself and Coworkers

Who am I?

@kyleshevlin

 

Senior Software Engineer at Fastly

 

Lover of JavaScript, hater of semicolons, and maintainer of a glorious beard.

 

Host of Second Career Devs podcast

What will we cover today?

  • Higher Order Functions
  • Purity
  • Currying & Partial Application
  • Composition

What is Functional Programming?

FP is...

A pain in the ass to define. Kinda.

 

The formal definition is:

 

A style of building the structure and elements of computer programs that treats computation as the evaluation of mathematical functions.

Unfortunately, that definition isn't very helpful.

 

It's not that different from telling you that the definition of painting is to apply paint to a surface.

 

But, we can define different styles of painting by examining the characteristics that differentiate them.

Ugh...

Pointilism

Impressionism

Characteristics of FP

  • Expressions over Statements
  • Avoids Mutations
  • Avoids Side Effects
  • Logic built  through composing pure functions

Imperative


  function doubleEach (arr) {
    const results = []

    for (let i = 0; i < arr.length; i++) {
      result.push(arr[i] * 2)
    }

    return results
  }

  const doubles = doubleEach([1,2,3])

Declarative


  function doubleEach (arr) {
    return arr.map(x => x * 2)
  }

  const doubles = doubleEach([1,2,3])

Functional


  const double = x => x * 2
  const map = fn => xs => xs.map(fn)

  const doubleEach = map(double) // returns a fn

  const doubles = doubleEach([1,2,3])

Higher Order Functions

A higher order function does at least one of the following:

  • Accepts a function as an argument
  • Returns a new function

Purity

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


  // Pure Function
  function add (x, y) {
    return x + y
  }

  // Impure Example
  let nextId = 0

  function impureAdd (x, y) {
    nextId++ // Ahh, gross mutation!
    return x + y
  }

Purity leads to easily testable functionality.

 

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

Currying & Partial Application

This

Haskell Brooks Curry

(super smart dude)

Not This

Curry

(super delicious food)

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

Canonical Example


  function curriedAdd (x) {
    return function (y) {
      return x + y
    }
  }

  // Or more elegantly with arrow functions

  const curriedAdd = x => y => x + y

Let's take a simple function and curry it.

Why was that useful?


  // Pretend we imported `add` from before
  const add5 = add(5)

  // Now we have a new fn with `5` partially applied
  // We can use add5 as many times as we want
  console.log(add5(10)) // 15
  console.log(add5(-5)) // 0
  console.log(add5(45)) // 50

Because a curried function can store some of its arguments in closure. This is called partial application.

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

Think back to high school.

 

Not the shitty parts you wish you could forget. But the math parts that you probably did forget.

 

Remember this at all? f(x) = y

In math, a function always maps one value to another aka pure functions.

 

You can also give the result of a function as the input to another function aka higher order functions. This is called composition.

 

It looks like: f(g(x)) = y

In programming, we rarely name functions a single letter. If you do, please stop, mkay?!

 

Thus, nesting functions to create compositions becomes really ugly, really fast.


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

  const string = 'Bob, the world is coming to an end'

  const warnBob =
    repeat(exclaim(scream(string)))

  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?

 

Let me introduce the compose function.


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

The compose function takes any number of functions as arguments and returns a function awaiting the value on which to operate.

 

Our new function feeds the output of the previous function as the input to the next one.

 

To read compose, you have to read the functions right-to-left.


  // Pretend we import `compose`, `scream`,
  // `exclaim`, and `repeat`
  const warnAdamantly = compose(
    repeat,
    exclaim,
    scream
  )

  const string = 'Bob, the world is coming to an end'

  const warnBob = warnAdamantly(string)

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

Exercises

Resources

Made with Slides.com