monade
monade
is a language agnostic framework to build monadiac compute pipelines, which is by far the cleanest representation of complex logic.
This makes WeaveDB completely modular from the ground up enabling multi-paradigm database types, such as document-based NoSQL, relational, and even vector database for AI.
monade
allows easy customizations and extentions of the core WeaveDB functionalities to build your own data pipelines. monade
is implemented in multiple languages such as JS, Rust, and LEAN for mathematical correctness, but we will cover the JS library on this page.
Installation
yarn add monade
Basics
of()
You can wrap any value with of()
and create a monad object.
import { of } from "monade"
const monad = of(3)
map()
You can construct a pipeline from a monad object chaining any functions with map()
.
import { of, map } from "monade"
const inc = (n) => n + 1
const double = (n) => n * 2
const square = (n) => n * n
const calc = of(3).map(inc).map(double).map(square)
// 3 + 1 = 4, 4 * 2 = 8, 8 * 8 = 64
Or you can build another pipeline on top.
const calc2 = calc.map(inc).map(double)
// calc2 = 64, 64 + 1 = 65, 65 * 2 = 130
val()
What of()
and map()
return is not the actual value, but a monad object which contains the value. You need val()
to extract the value. But this pattern makes compute pipelines indefinetely modular, composable, and extensible.
const val = calc.val() // => 64
const val2 = calc2.val() // => 130
chain()
If a chaining function returns a monad object instead of a bare value, you can use chain()
instead of map()
.
import { of, map, chain, val } from "monade"
const inc = (n) => n + 1
const double = (n) => n * 2
const square = (n) => n * n
// inc_double returns a monad instead of a value
const inc_double = (n)=> of(n).map(inc).map(double).map(square)
const val = of(3).chain(inc_double).square().val()
// (3 + 1) * 2 = 8, 8 * 8 = 64
tap()
If you want to insert a function without affecting the passed data, use tap()
.
import { tap } from "monad"
const calc = of(3).map(inc).map(double).tap(console.log).map(square)
// tap() doesn't affect the pipeline but execute side-effects
Kleisli Arrows
ka()
You can construct a pipeline without creating a monad object with an initial value. This pipeline is called Kleisli Arrow
in Category Theory of Mathematics.
import { ka, map } from "monade"
const inc = (n) => n + 1
const double = (n) => n * 2
const square = (n) => n * n
const arrow = ka().map(inc).map(double).map(square)
// const monad = of(3).map(inc).map(double).map(square)
fn()
Arrows are not bare functions, so you can extract the function from an arrow with fn()
.
The function returns a monad, so you can chain the extracted function with chain()
.
import { ka, chain, val } from "monade"
const calc = of(3).chain(arrow.fn()).val()
// 3 + 1 = 4, 4 * 2 = 8, 8 * 8 = 64
Errors
opt()
You can try...catch
an error or you can wrap the monad object with opt()
to safely get null
in case an error occurs anywhere in the pipeline.
import { opt, of, map, val } from "monade"
const dec = n => n - 1
const div10By = n => n === 0 ? throw Error() : 10 / n
try{
const calc = of(1).map(dec).map(div10By).val() // Error
}catch(e){}
const calc2 = opt(of(1).map(dec).map(div10By)) // => null
const calc3 = opt(of(3).map(dec).map(div10By)) // => 5
Devices
dev()
A device is a factory object to effectively construct monadiac pipelines with named functions with an arbitray number of added parameters.
The first argument ctx
is the context/data passed through the monadiac pipeline, and you can add arbutrary numbr parameter thereafter.
const calc = dev({
add: (ctx, n) => ctx + n,
mul: (ctx, n) => ctx * n,
sum: (ctx, ...nums) => nums.reduce((acc, n2) => acc + n2, ctx),
})
const val = calc(3).add(2).mul(3).sum(1, 2, 3).val()
// 3 + 2 = 5, 5 * 3 = 15, 15 + 1 + 2 + 3 = 21
Async Monads
In practice, many functions includes promises and asyncronous steps.
pof()
, pka()
, popt()
, and pdev()
are the async alternatives you can use with the exact same interface. With the p
family methods, you can mix sync functions and async functions in pipelines.
import { pof, pka, popt, pdev, map, chain, val } from "monade"
const wait = ms => new Promise(res => setTimeout(() => res(), ms))
const inc = async n => (await wait(100)) && n + 1 // async
const double = n => n * 2 // sync
const square = async n => (await wait(100)) && n * n // async
const calc = pof(3).map(inc).map(double).map(square)
const val = await calc.val() // => 64
const ka = pka().map(inc).map(double).map(square)
const val2 = await popt(pof(3).chain(ka.fn())) // => 64
const calc2 = pdev({
add: async (ctx, n2) => (await wait(100)) && ctx + n2, // async
mul: (ctx, n2) => ctx * n2, // sync
sum: async (ctx, ...nums) => // async
(await wait(100)) && nums.reduce((acc, n2) => acc + n2, ctx),
})
const val3 = await calc2(3).add(2).mul(3).sum(1, 2, 3).val() // => 21