Do notation

node v8.11.4
version: 12.0.0
endpointsharetweet
// While this should ideally be done as an AST transform using Babel, here's a naive, unperformant // implementation of do-notation for JS that uses some hilarious proxy hacks to "inspect" functions const mdo = M => (computation, context = {}) => { let vars = []; const varFactory = new Proxy( {}, { get: (_, v) => { if (context.hasOwnProperty(v)) return context[v]; vars.push(v); return v; } } ); const steps = computation(varFactory); if (steps.length === 0) { throw "Computation in do block cannot be empty"; } // Get the next step const next = steps[0]; if (steps.length === 1) { if (typeof next === "function") { return next(); } throw "Last step in a do block must be an expression and not an assignment"; } const remaining = vars => computation(vars).slice(1); if (typeof next === "function") { return M.chain(_ => mdo(M)(remaining, context))(next()); } const [v, m] = next; return M.chain(x => mdo(M)(remaining, { ...context, [v]: x }))(m()); }; // Usage examples in the cells that follow
const { Arr } = require("@masaeedu/fp") { const result = mdo(Arr)(({ x, y }) => [ [x, () => [1, 2]], [y, () => [3, 4]], () => Arr.of(x + y) ]); console.log(result); // => [4, 5, 5, 6] }
const { Maybe } = require("@masaeedu/fp") { const { Just, Nothing, match } = Maybe const show = match({ Nothing: "Nothing", Just: x => `Just(${x})` }) const validate = x => x > 5 ? Nothing : Just(x) const result = i => mdo(Maybe)(({ x }) => [ [x, () => validate(i)], () => validate(x * 2) ]) console.log(show(result(6))) // => Nothing console.log(show(result(1))) // => Just(2) console.log(show(result(3))) // => Nothing }
// Some examples of asynchronicity monads const { from, observe } = require("most") const Most = require("@most/core") { const result = mdo(Most)(({ x, y }) => [ [x, () => from([1, 2, 3, 4])], () => Most.of(x + 1) ]) observe(console.log, result) // logs: // => 2 // => 3 // => 4 // => 5 undefined; } const { Cont } = require("@masaeedu/fp") { const gettime = cb => cb(new Date().getTime() / 1000) const delay = x => d => cb => setTimeout(() => cb(x), d * 1000) const delay_ = delay(undefined) const result = mdo(Cont)(({ start, t1, t2 }) => [ [start, () => gettime], () => delay_(1), [t1, () => gettime], () => delay_(2), [t2, () => gettime], () => Cont.of([t1 - start, t2 - start]) ]); result(console.log); // eventually logs, approximately: // => [1, 3] undefined; } // Future work: // - A hypothetical "tediousmdo" that has more information and can be more efficient // (e.g. by using applicative for things that have no data dependency). Also does // not need to be declared in any particular order // ``` // { // const result = tediousmdo(Arr)({ // y: ({ x }) => [x, x], // x: ( ) => [1, 2], // out: ({ x, y }) => Arr.of(x + y) // }); // => [2, 2, 4, 4] // } // ``` // - Do this as a [Babel macro](https://github.com/kentcdodds/babel-plugin-macros) instead of at runtime
Loading…

no comments

    sign in to comment