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
- Figure out what it is
- Building a shiny toolbox
- 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
Keep calm and curry on
- 179