Skip to content

Instantly share code, notes, and snippets.

@davidchambers
Last active September 26, 2015 01:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidchambers/5997ba2991ec1c6c1109 to your computer and use it in GitHub Desktop.
Save davidchambers/5997ba2991ec1c6c1109 to your computer and use it in GitHub Desktop.
Sketch of possible (synchronous) IO and AsyncIO types in JavaScript
'use strict';
const fs = require('fs');
const R = require('ramda');
const S = require('sanctuary');
// unit :: ()
const unit = void 0;
// IO :: (() -> a) -> IO a
function IO(f) {
if (!(this instanceof IO)) {
return new IO(f);
}
this.value = f;
}
// IO#chain :: IO a ~> (a -> IO b) -> IO b
IO.prototype.chain = f => {
const io = this;
return IO(() => f(io.value(unit)).value(unit));
};
// IO#map :: IO a ~> (a -> b) -> IO b
IO.prototype.map = f => {
const io = this;
return IO(() => f(io.value(unit)));
};
// AsyncIO :: ((a -> ()) -> ()) -> AsyncIO a
function AsyncIO(f) {
if (!(this instanceof AsyncIO)) {
return new AsyncIO(f);
}
this.value = f;
}
// AsyncIO#chain :: AsyncIO a ~> (a -> AsyncIO b) -> AsyncIO b
AsyncIO.prototype.chain = f => {
const io = this;
return AsyncIO(c => { io.value(x => { f(x).value(c); }); });
};
// AsyncIO#map :: AsyncIO a ~> (a -> b) -> AsyncIO b
AsyncIO.prototype.map = f => {
const io = this;
return AsyncIO(c => { io.value(x => { c(f(x)); }); });
};
// runIO :: IO a -> a
const runIO = io => io.value(unit);
// runAsyncIO :: (a -> ()) -> AsyncIO a -> ()
const runAsyncIO = R.curry((f, aio) => { aio.value(f); });
// now :: IO Integer
const now = IO(() => Date.now());
// nowStr :: IO String
const nowStr = R.map(x => new Date(x).toISOString(), now);
// nowNowNowStr :: IO String
const nowNowNowStr =
S.pipe([R.chain(x => IO(() => x + runIO(nowStr) + '\n')),
R.chain(x => IO(() => x + runIO(nowStr) + '\n')),
R.chain(x => IO(() => x + runIO(nowStr) + '\n'))],
IO(S.K('')));
// printNowNowNowStr :: IO ()
const printNowNowNowStr = R.map(console.log, nowNowNowStr);
// readFile :: String -> AsyncIO (Either String String)
const readFile = filename =>
AsyncIO(c =>
fs.readFile(filename, {encoding: 'utf8'}, (err, text) => {
c(err == null ? S.Right(text) : S.Left(err.message));
})
);
// fooAsyncIO :: AsyncIO (Either String String)
const fooAsyncIO = readFile('foo.txt');
// barAsyncIO :: AsyncIO (Either String String)
const barAsyncIO = readFile('bar.txt');
// fooUpperAsyncIO :: AsyncIO (Either String String)
const fooUpperAsyncIO = R.map(R.map(R.toUpper), fooAsyncIO);
// fooUpperAsyncIO :: AsyncIO (Either String String)
const fooUpperBarAsyncIO =
R.chain(S.either(err => AsyncIO(c => { c(S.Left(err)); }),
foo => AsyncIO(c => {
barAsyncIO
.value(S.either(err => c(S.Left(err)),
bar => c(S.Right(foo + '---\n' + bar))));
})),
fooUpperAsyncIO);
// Side effects!
runIO(printNowNowNowStr);
// Side effects!
runAsyncIO(
either => {
process[S.is(S.Left, either) ? 'stderr' : 'stdout'].write(either.value);
},
fooUpperBarAsyncIO
);
'use strict';
const fs = require('fs');
const path = require('path');
const R = require('ramda');
const S = require('sanctuary');
const shell = require('shelljs');
const iolib = require('./io');
const AsyncIO = iolib.AsyncIO;
const runAsyncIO = iolib.runAsyncIO;
// readFile :: String -> AsyncIO (Either String Buffer)
const readFile = filename =>
AsyncIO(c => {
fs.readFile(filename, (err, buffer) => {
c(err == null ? S.Right(buffer) : S.Left(err.message));
});
});
// writeFile :: String -> Buffer -> AsyncIO (Either String String)
const writeFile = R.curry((buffer, filename) =>
AsyncIO(c => {
fs.writeFile(filename, buffer, err => {
c(err == null ? S.Right(filename) : S.Left(err.message));
});
})
);
// generateFilename :: AsyncIO String
const generateFilename = AsyncIO(c => {
c(path.join(__dirname,
'' + Date.now() + '_' + Math.floor(Math.random() * 1e10) +
'.pdf'));
});
// getText :: String -> AsyncIO (Either String String)
const getText = filename => AsyncIO(c => {
shell.exec(
'pdftotext "' + filename.replace(/"/g, "$&'$&'$&") + '" -f 1 -l 20 -',
{silent: true},
(code, data) => { c(code === 0 ? S.Right(data) : S.Left(data)); }
);
});
// parsePdf :: String -> Buffer -> AsyncIO (Either String String)
const parsePdf = R.curry((address, buffer) =>
S.pipe([R.chain(writeFile(buffer)),
R.chain(S.either(err => AsyncIO(c => c(S.Left(err))), getText)),
R.map(R.map(R.converge(R.ap,
S.compose(R.map(R.take), S.indexOf(address)),
S.Just))),
R.map(R.map(R.map(S.lines))),
R.map(R.map(R.chain(S.last))),
R.map(R.map(S.maybeToEither('Failed to find name in PDF'))),
R.map(R.chain(S.I)),
R.map(R.map(R.replace(/^!\d+! /, ''))),
R.map(R.map(R.split(' OR '))),
R.map(R.map(R.map(R.trim)))],
generateFilename)
);
// io :: AsyncIO (Either String String)
const io =
R.chain(S.either(err => AsyncIO(c => { c(S.Left(err)); }),
parsePdf('123 Main Street')),
readFile('document.pdf'));
runAsyncIO(console.log, io);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment