This codebase uses a functional programming (FP) approach and ideology. For anything which cannot be automatically linted for and resolved automatically, this document will be used to frame conventions used within the code and their rationale.
Use ramda for function composition, data transformation, higher-order functions and automatically curried interface. It is well tested, offers more functions than you will likely ever need to use, but has a core set of very clean utililty functions.
See converter for a good example of potential usage.
We also have eslint-plugin-ramda enabled, and it will guide you in some cases which can be statically found.
Unless explicitly specified, all exported functions should be curried if they are greater than arity 1. This aids equational reasoning, allows for partial application and allows for higher-order function transformation.
Incorrect Code
const wrapStringWithString = (outer, inner) => {
return outer[0] + inner + outer[1]
}
// INCORRECT: pipe(wrapStringWithString(['^', '$']))('cool')
Better Code
const wrapStringWithString = curry(function _wrapStringWithString(outer, inner) {
return outer[0] + inner + outer[1]
})
// CORRECT: pipe(wrapStringWithString(['^', '$']))('cool')
// INCORRECT: pipe(wrapStringWithString([]))('cool')
Even Better Code
const wrapStringWithString = curry(function _wrapStringWithString(outer, inner) {
return pathOr('', 0, outer) + inner + pathOr('', 1, outer)
})
// CORRECT: pipe(wrapStringWithString(['^', '$']))('cool')
// CORRECT: pipe(wrapStringWithString([]))('cool')
At the highest level (beneath curry
, likely), one should use named function expressions, because transpiled code often treats arrow functions and anonymous functions differently than named functions. This is minor but often aids debugging immensely.
Incorrect Code
const myFunction = curry((a, b) => a + b))
Better Code
const myFunction = curry(function _myFunction(a, b) { return a + b })
This codebase uses fluture
to provide a sane asynchronous interface which is lazy and monadic in nature. There are a number of attractive features which are a marked improvement from Promises / async
+ await
. If you are conceptually familiar with Promises, this is a good resource for understanding the trade-offs.
tl;dr
new Promise ((res) => { setTimeout (res, 200, 'Hello') })
.then (x => `${x} world!`)
.then (x => Promise.fromNode (done => fs.writeFile ('hello.txt', x, done)))
.then (console.log, console.error);
//vs
pipe(
() => Future((rej, res) => { setTimeout (res, 200, 'Hello') }),
map (x => `${x} world!`),
chain (x => Future.node (done => fs.writeFile ('hello.txt', x, done))),
fork(console.error)(console.log)
)()
See cli for a good example of potential usage.
- Use
map
to access / transform the contained value of the Future - Use
chain
to access / combine a different Future when you already have one - Use
fork
to invoke the actual asynchronous request. IF YOU DO NOT FORK, YOU WILL NEVER DO ANYTHING.