Beyond The Fat Arrow

Functional Programming in Javascript

The Complexity Iceberg
The Complexity Iceberg

Standard Disclaimer

  • I'm only pretending that I know what I'm talking about
  • So if you know better, please chime in!

What is Functional Programming?

  • A paradigm
  • Declarative as opposed to imperative in style
  • Rooted in mathematics (Lambda Calculus)
  • Favors composability
  • Seeks to eliminate side-effects when possible
  • Write pure functions

What is a pure function?

pure func·tion

  • No side-effects
  • No side-causes
  • Referential transparency
  • Saves us from the complexity iceberg

Side-Effect

  • Hidden output
  • What does it do that isn't part of the return value?
  • Side effects are necessary to do things
  • ...but dangerous when unintentional
  • Obey SRP!

An Example

const hourlyVisits = [100, 200, 300];

function visitDiffFromAvg(visits) {
    const sum = visits.reduce((t, h) => t + h);
    const hours = visits.length;
    const latest = visits.pop();

    return latest - (sum / hours);
};

// elsewhere...
const diffFromDailyAvg = visitDiffFromAvg(hourlyVisits);
const dailyPeakVisits = Math.max(...hourlyVisits); // 200

Side-Cause

  • Hidden input
  • What does it need that isn't in the argument list?
  • "A function with side-causes has undocumented assumptions about what external factors it depends on."

An Example

import moment from 'moment';

function getTodaysVisits(visits) {
    const today = moment().format('YYYY-MM-DD');

    return visits[today];
}

Referential Transparency

A pure function can be replaced by the value it returns.

Some More Examples

Another Side Effect

function cleanResponseKeys(response) {
    const keys = Object.keys(response);
    process(response);

    return keys.filter(item => name !== 'Matt');
}

Some array methods can cause unexpected side-effects if you don't know that they modify the array in place.

const x = [1, 2, 3];
const y = x.push(1);
console.log(x); // [1, 2, 3, 1]

const z = x.sort();
console.log(x); // [1, 1, 2, 3]
Mutator Methods Pure Methods
pop concat
push includes
reverse join
shift slice
sort map
splice filter
unshift reduce

MDN Docs

Is this pure?

import moment from 'moment';

const today = moment(), visits = {...};

function getYearAgoVisits(visits, date) {
    const yearAgo = date.subtract(1, 'year');

    return visits[yearAgo];
}

getYearAgoVisits(visits, today);
  • Iceberg, Right Ahead!

Why write pure functions?

  • Because we want to be happy!

Pure functions are...

  • Easier to think about
  • Easier to test
  • Easier to debug
  • Easier to ...maintain!
  • More Reusable
  • Memoizable

What do they cost?

  • Initial learning curve
  • Discipline
  • Flexibility
  • Can result in more memory usage
  • Missing anything?

How can we write pure functions?

  • Declare all inputs as arguments
  • Write unit tests, they can help identify side causes
  • Write functions that return values, rather than "do stuff"
  • Don't mutate data

One way to not mutate data: by convention

  • Use the array methods mentioned earlier
  • Make copies of things!
  • Discipline (again, and not just yours)

Object.assign()

const personalInfo = {
    name: 'Matt',
    town: 'Burlington',
    age: 29
};

const olderMatt = Object.assign(
    {},
    personalInfo,
    { age: 30 }
);

Spread Operator

A little syntactic sugar on top of Object.assign()

const olderMatt = {
    ...personalInfo,
    age: 30
};

// where do people look up what stage features are in?

Part of ES2017

But doing things by convention is hard...

Immutability

Benefits of immutability

  • "Like mutable data, but with one fewer feature: you can't edit it."
  • We often only need to read values
  • Protect your data from side effects
  • Cheap equality comparison

Immutability 2 Ways

  • External Libraries
  • Immutable.js
  • seamless-immutable
  • Object.freeze

Immutable.js

  • 12k stars
  • Persistence
  • Lazy Evaluation

seamless-immutable

  • 1.6k stars
  • Backwards compatible with vanilla arrays and objects

Object.freeze()

  • Out of the box only shallowly immutable
  • Updates fail silently or throw TypeError (in strict mode)
  • Object.isFrozen(obj)
  • Standard library
const x = Object.freeze({ a: 1, b: 2 });
x.a = 5;
console.log(x); // { a: 1, b: 2}

const y = Object.freeze({ a: 1, b: 2, c: [1, 2, 3]})
y.c[0] = 5;
console.log(y); // { a: 1, b: 2, c: [5, 2, 3]}
  • But you can deeply convert using both Object.freeze (deepFreeze) and any of the immutable libraries.

Why Immutability Is A Big Deal

With a vanilla Javascript array

const x = [1, 2, 3];
  • How to tell if x changed?
  • If I modify x[2], the reference to x doesn't change, but the value there has
  • In order to tell if x changed:
  • we can't just see if the reference has changed.
  • We need to check all of the values.

With an immutable array

const x = Immutable.List([1, 2, 3])
  • Because we can't mutate x, if that reference still exists, x hasn't changed
  • Comparisons now cost the same regardless of size

How it's used in the wild

shouldComponentUpdate(nextProps) {
    return this.props !== nextProps;
}
  • If the reference is equal, no need to render.
  • You are guaranteed the data is the same.
  • Also big benefits for memoization

So, how to write more functional code?

  • Declare all inputs
  • Be explicit about side-effects
  • Don't mutate your data
  • Better: use immutable data
  • Listen to Ben!
Thanks!
Thanks!

Places I Stole Ideas From