Functional Extravaganza

  • Andreas Møller
  • Stefano Vozza
  • James Chow

Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.

- John Fucking Carmack

  1. Figure out what it is
  2. Building a shiny toolbox
  3. Apply to real world

3 step plan


// A Function



const add = (x, y) => x + y


const three = add(1, 2)

// A Function


// add :: (Number, Number) -> Number
const add = (x, y) => x + y

// three :: Number
const three = add(1, 2)
// Compose
// compose :: (y -> z, ..., a -> b) -> (a -> z)
const compose = (...fs) => 
    x => fs.reduceRight((acc, f) => f(acc), x)
// getUrlQuery :: String -> String
const getUrlQuery = compose(last, x => x.split('?'))
// merge :: ({String: a}, {String: a}) -> {String: a}
const merge = (x, y) => Object.assign({}, x, y)

// fromPairs :: [[String,a]] -> {String: a}
const fromPairs = pairs => 
    pairs.reduce((obj, [k, v]) => merge(obj, {[k]: v}), {})

// splitPairs :: [String] -> [[String, String]]
const splitPairs = xs => 
    xs.map(x => x.split('='))

// parseQuery :: String -> {String: String}
const parseQuery = compose(fromPairs, splitPairs, x => x.split('&'))
// Curry


const curry = fn => {
    const curried  = (f, init) => 
        (...args) => {
          const acc = [...init, ...args]
          return acc.length >= f.length ? f(...acc) : curried(f, acc)
        }

    return curried(fn, [])
}
// Currying

// split :: String -> String -> [String]
const split = curry((separator, str) => str.split(separator)
// splitByAmp :: String -> [String]
const splitByAmp = split('&')
// meAndYou :: [String]
const meAndYou = splitByAmp("me&you") // ["me", "you"]
// Currying

// split :: String -> String -> [String]
const split = curry((separator, str) => str.split(separator)
// map :: (a -> b) -> [a] -> [b]
const map = curry((f, xs) => xs.map(f))

// reduce :: ((b, a) -> b) -> b -> [a] -> b
const reduce = curry((f, init, xs) -> xs.reduce(f, init))
// fromPairs :: [[String, a]] -> {String: a}
const fromPairs = reduce((acc, [k,v]) => merge(acc, {[k]: v}), {})
// parseQuery :: String -> {String: String}
const parseQuery = compose(fromPairs, map(split('=')), split('&'))

Functions

1 input 1 output

stateless

no sideeffects

composable

// Thinking in types

// prop :: String -> {String: a} -> a
const prop = curry((key, obj) -> obj[key])

// sum :: [Number] -> Number
const sum = reduce(add, 0)


// totalPrice :: [{price: Number}] -> Number
const totalPrice = compose(sum, map(prop('price')))

// filter :: (a -> Boolean) -> [a] -> [a]
const filter = curry((f, xs) -> xs.filter(f))

// totalPriceForProductsInStock :: [{price: Number}] -> Number
const totalPriceForProductsInStock = 
    compose(totalPrice, filter(prop('inStock')))

// Thinking in types

// indexBy :: (a -> String) -> [a] -> {String: a}
const indexBy = curry((f, xs) => 
    reduce((acc, x) => merge(acc, {[f(x)]: x}), {}, xs)

// innerJoin :: String -> String -> [a] -> [b] -> [c]
const innerJoin = curry((kx, ky, xs, ys) => {
    const dicty = indexBy(prop(ky), ys);
    return map(x => merge(x, {[kx]: dicty[x[kx]]}), xs)
});

// joinTalksAndSpeakers :: [Talk] -> [Speaker] -> [TalkWithSpeaker]
const joinTalksAndSpeakers = innerJoin('speaker', 'handle')

Questions?

Keep calm and curry on

By Andreas Møller