Keep calm and curry on
Andreas Møller
CTO @ Sentia
twitter.com/cullophid
github.com/cullophid
Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.
- John Fucking Carmack
// 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)// 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}), {})
// Compose// compose :: (y -> z, ..., a -> b) -> (a -> z)
const compose = (...fs) =>
fs.reduce(compose2, x => x)// compose2 :: ((y -> z), (x -> y)) -> (x -> z)
const compose2 = (g, f) => x => g(f(x))// Compose// getUrlQuery :: String -> String
const getUrlQuery = compose(last, x => x.split('?'))// 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 _curried = init => f =>
(...args) => {
const acc = [...init, ...args]
return acc.length >= f.length ? f(...acc) : _curried(acc)(f)
}
const curry = _curried([])
// 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))// parseQuery :: String -> {String: String}
const parseQuery = compose(fromPairs, map(split('=')), split('&'))// mapObj :: (a -> b) -> {String: a} -> {String: b}
const mapObj = curry((f, obj) =>
Object.keys(obj)
.reduce((acc, k) => merge(acc, {[k]: f(obj[k])}), {})// parseQuery :: String -> {String: String}
const parseQuery =
compose(mapObj(decodeURIComponent), fromPairs, map(split('=')), split('&'))
// buildQuery :: {String: String} -> String
const buildQuery =
compose(join('&'), map(join('=')), toPairs, mapObj(encodeURIComponent))
// Async
// getJSON :: String -> Promise JSON
const getJSON => url =>
fetch(url)
.then(r => r.json())
// getUserPermissions :: String -> [Permission]
const getUserPermissions => id =>
getJSON(`/api/users/${id}`)
.then(prop('permissions'))
// Async
// mapP :: (a -> b) -> Promise a -> Promise b
const mapP = curry((f, p) => p.then(f))// getUserPermissions :: String -> Promise [Permission]
const getUserPermissions =
compose(mapP(prop('permissions')), getJSON, concat('/api/users/'))// Async
// getUsers :: {String: String} -> Promise [User]
const getUsers =
compose(getJSON, concat('/api/users?'), buildQuery)// getAdminUsers :: {String: String} -> Promise [User]
const getAdminUsers =
compose(mapP(filter(prop('admin')), getUsers)
getAdminUsers({age: '32', city: 'copenhagen'})
.then(users => {
// ...
})We Are Hiring
Questions?
Keep calm and curry on (CopenhagenJS)
By Andreas Møller
Keep calm and curry on (CopenhagenJS)
- 262