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
add(1, 2) // 3
// Types
// add :: (Number, Number) -> Number
const add = (x, y) => x + y
// concat :: ([a], [a]) -> [a]
const concat = (xs, ys) => [...xs, ...ys]
concat([1, 2], [3, 4]) // [1, 2, 3, 4]
// reduce :: ((b, a) -> b), b, [a]) -> b
const reduce = (f, acc, [x, ...xs]) =>
x === undefined ? acc : reduce(f, f(acc, x), xs)
reduce(concat, [], [[1, 2], [3, 4]]) // [1, 2, 3, 4]
reduce(add, 0, [1, 2, 3]) // 6
// reducer :: (b, a) -> b
// using reduce
// sum :: [Number] -> Number
const sum = numbers => reduce(add, 0, numbers)
// flatten :: [[a]] -> [a]
const flatten = xs => reduce(concat, [], xs)
const reducer = (obj, [k, v]) => ({...obj, [k]: v})
// fromPairs :: [[String, a]] -> {String: a}
const fromPairs = pairs => reduce(reducer, {}, pairs)
fromPairs([['one', 1], ['two', 2]]) // {one:1, two: 2}
// using reduce
// reducer :: (b, a) -> b
const reducer = (acc, x) => [...acc, f(x)]
// reducer :: (b, a) -> b
const reducer = (acc, e) => f(e) ? [...acc, e] : acc
// map :: ((a -> b), [a]) -> [b]
const map = (f, xs) =>
reduce(reducer, [], xs)
// filter :: ((a -> Boolean), [a]) -> [a]
const filter = (f, xs) =>
reduce(reducer, [], xs)
// using map
// props :: ([String], {String: a}) -> [a]
const props = (keys, object) => map(k => prop(k, object), keys)
// pluck :: (String, [{String: a}]) -> [a]
const pluck = (key, xs) => map(x => prop(key, x), xs)
// Compose
// compose2 = (b -> c, a -> b) -> (a -> c)
compose2 = (f, g) => x => f(g(x)
// flatten :: [[a]] -> [a]
// sum :: [Number] -> Number
// flatSum :: [[Number]] -> Number
const flatSum = compose2(sum, flatten)
flatSum([[1, 2], [3, 4]]) // 10
// Compose
// compose = (y -> z, ..., a -> b) -> (a -> z)
const compose = (...fs) => x => reduce(compose2, x => x, fs)
// flatSumTotalBiggerThan100 :: [[Number]] -> Boolean
const flatSumBiggerThan100 = compose(x => x > 100, sum, flatten)
// Limitation
// only works when functions take 1 argument
compose(map, sum) // :(
// Currying
// prop :: String -> {String: a} -> a
const prop = key => obj => obj[key]
// prop :: (String, {String: a}) -> a
const prop (key, obj) => obj[key]
// Currying
// sumNumbers :: {numbers: [Number]} -> Number
const sumNumbers = compose(sum, prop('numbers'))
sumNumbers({numbers: [1, 2, 3]}) // 6
// Currying
const id = prop('id')(obj)
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
const prop = curry((key, obj) => obj[key])
prop('id', obj) === prop('id')(obj)
// Currying
// map :: (a -> b) -> [a] -> [b]
const map = curry((f, xs) =>
reduce((acc, x) => [...acc, f(x)], [], xs))
// getTotalPrice :: [{price: Number}] -> Number
const getTotalPrice = compose(sum, map(prop('price')))
// filter = (a -> Boolean) -> [a] -> [a]
const filter =
curry((f, xs) => reduce((acc, x) => f(x) ? [...acc, x] : acc, [], xs))
// getTotalPrice :: [{price: Number}] -> Number
const getTotalPrice =
compose(sum, map(prop('price')), filter(prop('inStock')))
/*
* A function is a relation between 1 input and 1 output
*/
/*
* Functions are stateless
*
* Given the same input
* they will always return the same output
*/
/*
* The only thing a function does is
* compute an output value
*/
/*
* Functions are composable like legos
*/
/*
* Lego is awesome!
*/
// The secret sauce... Lining up types
// getTotal :: [{price: Number}] -> Number
const getTotal = compose(sum, map(prop('price')))
// parseQuery :: String -> {String: String}
const parseQuery = compose(fromPairs, map(split('=')), split('&'))
// questions ?
Copy of Keep calm and curry on
By James Chow
Copy of Keep calm and curry on
- 98