Who Gives a Fuck About A Monad?

Functional programming for people who hate math.

by Jason Lengstorf
@jlengstorf | jason@lengstorf.com

Slides: git.io/vyzvq

Who I Am

Who I Am

  • Building web shit since 2003
  • Huge process & efficiency nerd
  • Work-life balance advocate / consultant
  • Senior developer at IBM
  • Author of 3 books on development stuff
  • World champion purveyor of bear hugs

Before we start:

What I’m Not Here to Do

I’m not here to “convert” you.

I’m not trying to convince you to write all of your code a certain way.

As web developers, dogmacan get us left behind.

What I Am Here to Do

Learn techniques to improve code quality in many situations...

...but not all of them.

Cool?

Cool.

What is functional programming?

What does this mean?

(s → a) → ((a, s) → s) → Lens s a
Lens s a = Functor f => (a → f a) → s → f s

(If you do understand this, now would be a good time to fake an urgent phone call.)

The examples don't help:

var xLens = R.lens(R.prop('x'), R.assoc('x'));

R.view(xLens, {x: 1, y: 2});           //=> 1
R.set(xLens, 4, {x: 1, y: 2});         //=> {x: 4, y: 2}
R.over(xLens, R.negate, {x: 1, y: 2}); //=> {x: -1, y: 2}

And, of course: the jargon

monads composability referential transparency arity side-effects
immutability currying category theory tuples variadic functions

This is nerd stuff for Nerds

...or is it?

What is functional programming?

(The jargon-free version)

Functional programming
helps us write code that is:

  • Easier to understand and debug
  • Cheaper and easier to maintain
  • More legible
  • More reusable
  • More testable
  • Less error-prone

To accomplish this, our code follows a few rules:

  1. Use functions over loops whenever possible
  2. Always return the same result given the same arguments
  3. Write functions that do one thing

Let's learn how

Rule #1:

Functions Are Better Than

Loops

Scenario: This Beverage List

Is Broken

const people = [
  {
    name: 'Marisa',
    spirit_animal: 'koala',
    beverages: [
      'tea',
      'vodka', // <-- WTF is this?!
    ],
  },
  {
    name: 'Jason',
    spirit_animal: 'bear',
    beverages: [
      'coffee',
      'whiskey',
    ],
  },
];

First: a quick bit of jargon vocabulary.

Imperative Programming

Code that explicitly describes how to do something.

An Imperative Approach to Eating:

When Harry Met Sally
"I'd like the pie heated and I don't want the ice cream on top, I want it on the side, and I'd like strawberry instead of vanilla if you have it. If not, then no ice cream, just whipped cream — but only if it's real; if it's out of the can then nothing."

Declarative Programming

Code that describes what the result should be.

A Declarative Approach to Eating:

Parks and Recreation
"Give me all the bacon and eggs you have."

Got it?

Let’s look at some code to fix that beverage list.

An Imperative Solution

let fixed = [];
for (let person of people) {
  if (person.beverages) {
    for (let beverage in person.beverages) {
      if (person.beverages[beverage] === 'vodka') {
        person.beverages[beverage] = 'whiskey (FTFY)';
      }
    }
  }
  fixed.push(person);
}

Try this live: https://goo.gl/aobHwP

Beware the Pyramid of Doom!

Pyramid of Doom by Contenebratio
Credit: Contenebratio

A Declarative Solution

const fixBeverage = (str) => (
  str.replace('vodka', 'whiskey (FTFY)')
);

const helpIfConfused = (person) => ({
  ...person,
  beverages: person.beverages.map(fixBeverage)
});

const fixed = people.map(helpIfConfused);

Try this live: https://goo.gl/HO7tps

The Result Is Identical

[
  {
    "name": "Marisa",
    "spirit_animal": "koala",
    "beverages": [
      "tea",
      "whiskey (FTFY)"
    ]
  },
  {
    "name": "Jason",
    "spirit_animal": "bear",
    "beverages": [
      "coffee",
      "whiskey"
    ]
  }
]

Okay but...

What the eff's a

map?

Array.prototype.map()

Applies a function to each element of an array.

Which means that this...

const double = num => num * 2;
const numbers = [1, 2, 3];
const nextNumbers = [];
for (let x in numbers) {
  nextNumbers[x] = double(numbers[x]);
}

Try this live: https://goo.gl/30SfNQ

...is the same as this.

const double = num => num * 2;
const numbers = [1, 2, 3];
const nextNumbers = numbers.map(double);

Try this live: https://goo.gl/7mYn2x

Protip: get comfortable with array methods

  • Array.prototype.filter()
  • Array.prototype.sort()
  • Array.prototype.every()
  • ...and many more.

Rule #2:

The Same InputAlwaysReturns the
Same Result

"Wait. That's dumb. My code already does that."

Are you sure?

The Jargon:

Pure vs. Impure

Scenario: Tell People About Your Favorite Thing

An Impure Solution

let myFavoriteThing = 'whiskey';

function describeMyFavoriteThing() {
  return `I prefer to drink quality ${myFavoriteThing}.`;
}

Try this live: https://goo.gl/IQeD45

Wait! New feature request:

function clarifyFavoriteThing() {
  myFavoriteThing = 'aged ' + myFavoriteThing;
}

Try this live: https://goo.gl/FEUtUZ

Then Legal Gets Involved:

function makeFamilyFriendly() {
  myFavoriteThing = 'scented bubble bath';
}

Try this live: https://goo.gl/zgRpEm

Good Luck Debugging This:

let myFavoriteThing = 'whiskey';

clarifyFavoriteThing();
describeMyFavoriteThing();
//=>"I prefer to drink quality aged whiskey." (yay!)

// ...probably a bunch of additional code...
makeFamilyFriendly();
// ...probably more additional code...

describeMyFavoriteThing();
//=>"I prefer to drink quality scented bubble bath." (!)

Try this live: https://goo.gl/xFPtJF

A Pure Approach

function describeMyFavoriteThing(beverage) {
  return `I prefer to drink quality ${beverage}.`;
}

function clarifyFavoriteThing(favoriteThing) {
  return `aged ${favoriteThing}`;
}

function makeFamilyFriendly() {
  return 'scented bubble bath';
}

Try this live: https://goo.gl/qDtJeu

We can clearly follow what's happening:

const myFavoriteThing = 'whiskey';
const clarified = clarifyFavoriteThing(myFavoriteThing);
const newFavorite = makeFamilyFriendly();

describeMyFavoriteThing(myFavoriteThing);
//=>"I prefer to drink quality whiskey."

describeMyFavoriteThing(clarified);
//=>"I prefer to drink quality aged whiskey."

describeMyFavoriteThing(newFavorite);
//=>"I prefer to drink quality scented bubble bath."

Try this live: https://goo.gl/qDtJeu

Debugging is easy:

// start debugging here: ↓ ↓ ↓ ↓ ↓
describeMyFavoriteThing(newFavorite);

Try this live: https://goo.gl/qDtJeu

This Is Called Referential Transparency

This means the function call can always be replaced with its return value without breaking the program.

We can replace any pure function with its return value & get the same result:

const chz = `I love ${clarifyFavoriteThing('cheddar')}`;

...is the same as...

const chz = 'I love aged cheddar.';

Testing's Never Been Easier:

expect(clarifyFavoriteThing('cheddar'))
  .toEqual('aged cheddar');

This enables a magical functional technique:Composition

function describeMyFavoriteThing(beverage) {
  return `I prefer to drink quality ${beverage}.`;
}
function clarifyFavoriteThing(favoriteThing) {
  return `aged ${favoriteThing}`;
}
const showClarifiedFavorite = R.compose(
  describeMyFavoriteThing,
  clarifyFavoriteThing
);
const result = showClarifiedFavorite('whiskey');

Try this live:https://goo.gl/wQFhZK

Using Composition is the same as calling this:

describeMyFavoriteThing(clarifyFavoriteThing('whiskey'));

It just means we don't need the data up front.

Rule #3:

Each Function DoesOne Thing

Scenario: Filter by Genre &Sort by Artist

const albums = [
  {
    name: 'Middle Cyclone',
    artist: 'Neko Case',
    genre: 'indie',
  },
  {
    name: 'Highly Refined Pirates',
    artist: 'Minus The Bear',
    genre: 'rock',
  },
  {
    name: 'Rabbit Fur Coat',
    artist: 'Jenny Lewis',
    genre: 'indie',
  },
  {
    name: 'Black on Both Sides',
    artist: 'Mos Def',
    genre: 'hip-hop',
  },
];

An All-In-One Solution

function getOnlyIndie(albums) {
  let filtered = [];
  for (let album of albums) {
    if (album.genre === 'indie') {
      filtered.push(album);
    }
  }
  filtered.sort((album1, album2) => {
    if (album1.artist === album2.artist) return 0;
    return album1.artist > album2.artist ? 1 : -1;
  });
  return filtered;
}

Try this live: https://goo.gl/lPK1CD

One Function Per Step

const byArtistAsc = (album1, album2) => {
  if (album1.artist === album2.artist) {
    return 0;
  }
  
  return album1.artist > album2.artist ? 1 : -1;
};

const getOnlyIndie = album => album.genre === 'indie';

albums.filter(getOnlyIndie).sort(byArtistAsc);

Try this live: https://goo.gl/HXscSK

But Wait!

We can do even better.

This is so freakin' reusable!

const filterByGenre = R.curry((genre, album) => {
  return album.genre === genre;
});

// Create similar functions without duplicate code
const onlyHipHop = filterByGenre('hip-hop');
const onlyIndie = filterByGenre('indie');
const onlyRock = filterByGenre('rock');

const hipHop = albums.filter(onlyHipHop);

Try this live: https://goo.gl/qPJhZW

What's That Curry Thing?

Not this curry:

curry

This Curry:

curry

R.curry()

Allows functions to be called in stages.

Which allows us to do this:

function addNumbers(num1, num2) {
  return num1 + num2;
}

const curriedAdd = R.curry(addNumbers);
const add4 = curriedAdd(4);

add4(2); //=> 6
add4(7); //=> 11

Try this live: https://goo.gl/jVjOW3

Let's Recap

Functional programming
helps us write code that is:

  • Easier to understand and debug
  • Cheaper and easier to maintain
  • More legible
  • More reusable
  • More testable
  • Less error-prone

To accomplish this, our code follows a few rules:

  1. Functions are better than loops
  2. Always return the same result given the same arguments
  3. Write functions that do one thing

You can go waaay deeper...

...but just these three techniques will save you hours of headaches and make your code better and easier to deal with going forward.

Questions?

Jason Lengstorf
@jlengstorf | jason@lengstorf.com

Resources

  1. Favoring Curry
  2. Introduction to Composition in JS
  3. Ramda, a functional library for JS
  4. Array methods in JavaScript
  5. Imperative vs Declarative Programming

Tweet: @jlengstorf #webrebels