Skip to content
This repository has been archived by the owner on Feb 18, 2022. It is now read-only.

Commit

Permalink
Revenge of the callbacks!
Browse files Browse the repository at this point in the history
  • Loading branch information
masaeedu committed Jul 15, 2018
1 parent 0ea31b0 commit 3133e30
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The solutions are best read in the following order:
- [__coroutines-bluebird.js__](./coroutines-bluebird.js)
- [__await.js__](./await.js)
- [__futures.js__](./futures.js)
- [__callbacks-revenge.js__](./callbacks-revenge.js)

The above ordering is suggested reading order only – not grade. Solutions are
graded on the following criteria:
Expand Down
53 changes: 53 additions & 0 deletions callbacks-revenge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

const fs = require('fs');

const exit0 = require('./common/exit0');
const exit1 = require('./common/exit1');
const {
Arr,
Fn,
Str,
Path,
NodeEither,
GenericEitherT,
Cont,
} = require('./common/revengeutils');

const getpath = Path.combine(process.argv[2]);

// A monad for working with continuations that contain NodeEithers
// :: type NC r e x = GenericEither (Cont r) NodeEither e x
// :: = Cont r (NodeEither e x)
const NC = GenericEitherT(NodeEither)(Cont);

// Standard NodeJS APIs need a little massaging to be valid continuations
// - Callback must be a curried, final argument
// - Can only pass a single argument to callback (an [err, ...data] array is fine)
// :: Path -> NC () String String
const readFile = path => cb => fs.readFile(path, 'utf8', (...args) => cb(args));

// Main
// :: String -> NC () String String
const readAllFiles = Fn.pipe([
getpath, // :: Path
readFile, // :: NC () String String
NC.map(Str.lines), // :: NC () String [String]
NC.map(Arr.map(getpath)), // :: NC () String [Path]
NC.chain(Arr.traverse(NC)(readFile)), // :: NC () String [String]
NC.map(Str.join('')), // :: NC () String String
]);

// :: NC () String String
const result = readAllFiles('index.txt');

// Remember that:
// :: type NC r e x = Cont r (NodeEither e x)
// so...
// :: NC () String String = Cont () (NodeEither String String)
// :: = ((NodeEither String String) -> ()) -> ()

// :: NodeEither String String -> ⊥
const fork = NodeEither.match({Left: exit1, Right: exit0});
const main = () => result(fork); // :: ()
main();
91 changes: 91 additions & 0 deletions common/revengeutils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
'use strict';

const path = require('path');


const derivemonad = M => {
const {of, chain} = M;

const map = f => chain(x => of(f(x)));
const lift2 = f => mx => my => chain(x => chain(y => of(f(x)(y)))(my))(mx);

return Object.assign({map, lift2}, M);
};

const Arr = (() => {
const of = x => [x];
const map = f => x => x.map(f);

const foldl = f => z => xs => xs.reduce((p, c) => f(p)(c), z);

const empty = [];
const append = a => b => [...a, ...b];

const sequence = A => foldl(A.lift2(b => a => append(b)(of(a))))(A.of(empty));
const traverse = A => f => xs => sequence(A)(map(f)(xs));

return {map, foldl, traverse};
})();

const Fn = (() => {
const id = x => x;
const compose = f => g => a => f(g(a));
const flip = f => x => y => f(y)(x);
const pipe = Arr.foldl(flip(compose))(id);

return {id, compose, flip, pipe, '.': compose};
})();

const Str = (() => {
const trim = s => s.trim();
const join = sep => arr => arr.join(sep);
const split = sep => s => s.split(sep);
const lines = Fn['.'](split('\n'))(trim);

return {split, lines, join, trim};
})();

const Path = (() => {
const combine = base => sub => path.join(base, sub);
return {combine};
})();

// An Either interpretation of Node style first-element-falsy arrays
// :: type NodeEither e d = [Maybe e, ...d]
const NodeEither = (() => {
const Left = l => [l];
const Right = (...r) => [null, ...r];

const match = ({Left, Right}) => ([e, ...x]) => e ? Left(e) : Right(...x);

return {Left, Right, match};
})();

// The GenericEitherT monad transformer
// :: type GenericEitherT e m l r = m (e l r)
const GenericEitherT = E => M => {
const {Left, Right, match} = E;

// :: x -> m (e l x)
const of = x => M.of(Right(x));

// :: (a -> m (e l b)) -> m (e l a) -> m (e l b)
const chain = f =>
Fn['.'](M.chain)(match)({
Left: l => M.of(Left(l)),
Right: f,
});

return derivemonad({of, chain});
};

// The continuation monad
// :: type Cont r a = (a -> r) -> r
const Cont = (() => {
const of = x => cb => cb(x);
const chain = f => m => cb => m(x => f(x)(cb));

return derivemonad({of, chain});
})();

module.exports = {Arr, Fn, Str, Path, NodeEither, GenericEitherT, Cont};
1 change: 1 addition & 0 deletions test
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,4 @@ test coroutines-co.js
test coroutines-bluebird.js
test await.js
test futures.js
test callbacks-revenge.js

0 comments on commit 3133e30

Please sign in to comment.