Last active
September 26, 2015 01:17
-
-
Save davidchambers/5997ba2991ec1c6c1109 to your computer and use it in GitHub Desktop.
Sketch of possible (synchronous) IO and AsyncIO types in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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