Functional Programming

with JavaScript

- Greg Douglas @GWShark0

Why Functional Programming?

A monad is just a monoid in the category of endofunctors.

What's the problem?

Functor

Category

Catamorphism

Zygohistomorphic Prepromorphism

Future

Closure

Setoid

Option

Foldable

Lambda

Why JavaScript?!

Map

const numbers = [1, 2, 3];

const result = numbers.map(x => x + 1);

//=> [ 2, 3, 4 ]

const numbers = [1, 2, 3];
const result = [];

for (let i = 0; i < numbers.length; i++) {
  result.push(numbers[i] + 1);
}

//=> [ 2, 3, 4 ]

For Loop

Map

const numbers = [1, 2, 3];

const result = numbers.map(x => x + 1);

//=> [ 2, 3, 4 ]
const numbers = [1, 2, 3];

const add = (a, b) => a + b;

numbers.map(n => add(n, 2));

//=> [ 3, 4, 5 ]
numbers.map(add(2)); // ??

Partial Application

// ES5
function add(a) {
    return function (b) {
        return a + b;
    }
}

// ES6
const add = (a) => (b) => a + b;
const addOne = add(1);
//=> function (b) { return 1 + b; }

const addTwo = add(2);
const addMilllion = add(1000000);

Partial Application

const add = (a) => (b) => a + b;

const addTwo = add(2);

const numbers = [1, 2, 3];

numbers.map(addTwo);
//=> [ 3, 4, 5 ]

numbers.map(add(1000000));
//=> [ 1000001, 1000002, 1000003 ]
function add (a) {
  return function (b) {
    return function (c) {
      return a + b + c;
    };
  };
}

add(1)(2)(3);
// 6

Currying

function add(a, b, c) {
  return a + b + c;
}

add(1) //=> NaN
add(1, 2) //=> NaN
add(1, 2, 3) //=> 6
const curriedAdd = curry(add);

curriedAdd(1) //=> Function
curriedAdd(1)(2) //=> Function
curriedAdd(1)(2)(3) //=> 6
const curry = (f, arr = []) => {
  return (...args) => {
    return (
      (a) => {
        return (a.length === f.length)
          ? f(...a)
          : curry(f, a);
      }
    )([...arr, ...args])
  }
}
const identity = (x) => x;

const curriedIdentity = curry(identity);
// => Function
const curry = (f, arr = []) => {
  return (...args) => { //=> [ 1 ]
    return (
      (a) => { //=> [ 1 ]
        return (a.length === f.length) //=> true
          ? f(...a)
          : curry(f, a);
      }
    )([...arr, ...args]) //=> [ 1 ]
  }
}
const identity = (x) => x;

const curriedIdentity = curry(identity);
// => Function

curriedIdentity(1);
// => 1
const curry = (f, arr = []) => {
  return (...args) => {
    return (
      (a) => {
        return (a.length === f.length)
          ? f(...a)
          : curry(f, a);
      }
    )([...arr, ...args])
  }
}
const add = (a, b, c) => a + b + c;

const curriedAdd = curry(add);
// => Function
const add = (a, b, c) => a + b + c;

const curriedAdd = curry(add);
// => Function

curriedAdd(1);
// => Function
const curry = (f, arr = []) => {
  return (...args) => { //=> [ 1 ]
    return (
      (a) => { //=> [ 1 ]
        return (a.length === f.length) //=> false
          ? f(...a)
          : curry(f, a);
      }
    )([...arr, ...args]) //=> [ 1 ]
  }
}
const curry = (f, arr = []) => {
  return (...args) => { //=> [ 2 ]
    return (
      (a) => { //=> [ 1, 2 ]
        return (a.length === f.length) //=> false
          ? f(...a)
          : curry(f, a);
      }
    )([...arr, ...args]) //=> [ 1, 2 ]
  }
}
const add = (a, b, c) => a + b + c;

const curriedAdd = curry(add);
// => Function

curriedAdd(1)(2);
// => Function
const curry = (f, arr = []) => {
  return (...args) => { //=> [ 3 ]
    return (
      (a) => { //=> [ 1, 2, 3 ]
        return (a.length === f.length) //=> true
          ? f(...a)
          : curry(f, a);
      }
    )([...arr, ...args]) //=> [ 1, 2, 3 ]
  }
}
const add = (a, b, c) => a + b + c;

const curriedAdd = curry(add);
// => Function

curriedAdd(1)(2)(3);
// => 6

lodash/fp

lodash with its methods wrapped to produce immutable auto-curried iteratee-first data-last methods

function get(property, object) {
  return object[property];
}

const people = [ { name: 'Alice' }, { name: 'Bob' } ];
function getPersonName(person) {
  return get('name', person);
}

const names = people.map(getPersonName);
//=> ['Alice', 'Bob']
import curry from 'lodash/fp/curry';

get = curry(get);

const names = people.map(get('name'));
//=> ['Alice', 'Bob']
import split from 'lodash/split';

const words = (str) => split(' ', str);

words('The quick brown fox');
//=> [ 'The', 'quick', 'brown', 'fox' ]
import split from 'lodash/fp/split';

const words = split(' ');

words('The quick brown fox');
//=> [ 'The', 'quick', 'brown', 'fox' ]

Normal

Auto-curried

import { get, map } from 'lodash';

const names = map(people, (person) => get(person, 'name'));
//=> ['Alice', 'Bob']

Normal

Data-last

const people = [ { name: 'Alice' }, { name: 'Bob' } ];
import { get, map } from 'lodash/fp';

const names = map(get('name'))(people);
//=> ['Alice', 'Bob']
import { get, map } from 'lodash/fp';

const getNames = map(get('name'));

const names = getNames(people);
//=> ['Alice', 'Bob']

Point-Free

foo(function(v) {
  return bar(v);
});
foo(function(v) {
  return bar(v);
});

foo(bar);
function isOdd(n) {
  return n % 2 == 1;
}

function isEven(n) {
  return !isOdd(n);
}

isEven(4); // true
function negate(fn) {
  return function negated(...args) {
    return !fn(...args);
  };
}

function isOdd(n) {
  return n % 2 == 1;
}

const isEven = negate(isOdd);

isEven(4); // true

Composition

function compose(f, g) {
  return function(x) {
    return f(g(x));
  }
}
compose(reverse, capitalize)('hello');
// => 'olleH'

'olleH' <= 'Hello' <= 'hello'

compose(capitalize, reverse)('hello');
// => 'Olleh'

'Olleh' <= 'olleh' <= 'hello'

function compose(f, g) {
  return function(x) {
    return f(g(x));
  }
}
pipe(capitalize, reverse)('hello');
// => 'olleH'

'hello' => 'Hello' => 'olleH'

function pipe(f, g) {
  return function(x) {
    return g(f(x));
  }
}

Example

const scientists = [
  {
    firstName: 'Stephen',
    lastName: 'Hawking',
    focus: 'Physics',
  },
  {
    firstName: 'Charles',
    lastName: 'Darwin',
    focus: 'Biology',
  },
  {
    firstName: 'Marie',
    lastName: 'Curie',
    focus: 'Physics',
  },
];

const formatName = (p) => `${p.firstName} ${p.lastName}`;
import { filter, sortBy, map } from 'lodash';

const filtered = filter(scientists, { focus: 'Physics' });

const sorted = sortBy(filtered, 'firstName');

map(sorted, formatName);
// => [ 'Marie Curie', 'Stephen Hawking' ]
import { map, sortBy, filter } from 'lodash';

map(
  sortBy(
    filter(scientists, { focus: 'Physics' }),
    'firstName'
  ),
  formatName
);
// => [ 'Marie Curie', 'Stephen Hawking' ]
import { chain } from 'lodash';

chain(scientists)
  .filter({ focus: 'Physics' })
  .sortBy('firstName')
  .map(formatName)
  .value();
// => [ 'Marie Curie', 'Stephen Hawking' ]
import { compose, map, sortBy, filter } from 'lodash/fp';

compose(
  map(formatName),
  sortBy('firstName'),
  filter({ focus: 'Physics' }),
)(scientists);
// => [ 'Marie Curie', 'Stephen Hawking' ]
import { pipe, filter, sortBy, map } from 'lodash/fp';

pipe(
  filter({ focus: 'Physics' }),
  sortBy('firstName'),
  map(formatName),
)(scientists);
// => [ 'Marie Curie', 'Stephen Hawking' ]

Questions

- Hardcore Functional Programming in JavaScript
  https://frontendmasters.com/courses/functional-javascript/

 

- Mostly Adequate Guide to Functional Programming
  https://drboolean.gitbooks.io/mostly-adequate-guide/

Pure Functions

A function can be considered pure if it:

  • Evaluates to the same result given the same arguments
  • Does not cause observable side effects
f(x) = x^2
f(x)=x2f(x) = x^2
function square(x) {
  return Math.pow(x, 2);
}

square(1);
// => 1

square(2);
// => 4

square(-3);
// => 9
let b = 0;

function foo(a) {
  b++;
  return a + b;
}

foo(0);
//=> 1

foo(0);
//=> 2

foo(0);
//=> 3

Side Effects

  • I/O
  • Network Requests
  • Mutation of global state
  • DOM Manipulation
  • Timestamps
  • Math.random()

Advantages

  • Easier to reason about
  • Portable
  • Easier to test
  • Memoizable
  • Parallelizable

Functional Programming in

By unicode

Functional Programming in

  • 165