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