Skip to content

Instantly share code, notes, and snippets.

@TrevorBasinger
Created January 22, 2015 23:15
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save TrevorBasinger/53acd141b41b83e13602 to your computer and use it in GitHub Desktop.
Save TrevorBasinger/53acd141b41b83e13602 to your computer and use it in GitHub Desktop.
require ('./globals')(global); // Mixed in Ramda to global here
var Task = require ('data.future'),
M = require ('control.monads'),
State = require ('fantasy-states'),
Reader = require ('fantasy-readers'),
Tuple2 = require ('fantasy-tuples').Tuple2,
Maybe = require ('data.maybe'),
ST = State.StateT (Task),
App = Reader.ReaderT (ST);
App.liftT = function (t) {
return App.liftR (ST (function (s) {
return new Task (function (reject, resolve) {
t.fork (reject, function (data) {
return resolve (Tuple2 (data, s));
});
});
}));
};
App.liftR = App.lift;
App.lift = compose (App.liftR, ST.lift);
App.prototype.map = function (f) { return this.chain (compose (App.of, f)); };
// For inserting custom lift function
App.prototype.mapLift = curry (function (lift, f) {
return this.chain (compose (lift, f));
});
var
// getPathFromRequest :: Request -> String
getPathFromRequest = compose (get ('path'), get ('file'), get ('files')),
// savePath :: App -> App
savePath = App.ask.chain (function (r) {
var path = getPathFromRequest (r);
return App.liftR (ST.modify ( mixin ({ path: path }) ));
}),
// getPath :: App -> App
getPath = App.liftR (ST.get).map (get ('path')),
// getFileHash :: Path -> Task Hash
getFileHash = function (path) {
return new Task (function (reject, resolve) {
require ('child_process').exec ('md5sum '+ path, function (err, stdout, stderr) {
if (err) { return reject (err); }
return compose (resolve, head, split (/\s/)) (stdout);
});
});
},
// saveHash :: Hash -> App
saveHash = function (hash) {
return App.liftR (ST.modify ( mixin ({ hash: hash }) ));
},
getFileHash = M.sequence (App, [ savePath, getPath .chain (compose ( App.liftT, getFileHash )) ]),
nil = null;
//==============================================================================
// Impure
//==============================================================================
var express = require ('express'),
bodyParser = require ('body-parser'),
cookieParser = require ('cookie-parser'),
multer = require ('multer'),
app = express (),
errJson = curry (function (code, res, data) { res.send (code, data); }),
succJson = curry (function (res, data) { res.json (data || {}); }),
appResponse = curry (function (app, req, res) {
var defaultState = {};
app.run (req)
.evalState (defaultState)
.fork (
errJson (401, res),
succJson (res)
);
}),
ask = App.ask,
nil = null;
app.use (express.static (__dirname + '/public'));
app.use (bodyParser.urlencoded ({ extended: true }));
app.use (bodyParser.json ());
app.use (multer ({ dest: './uploads/' }));
app.get ('/', function (req, res) {
res.send (compose (toString, add (2))(4));
});
app.post ('/upload', appResponse (getFileHash));
app.listen (7171);
@TrevorBasinger
Copy link
Author

I'd like to get a little feedback on my style of functional JavaScript. Is it too messy or hard to read? I tried to keep the sample size relatively small, but most of my code is starting too look like this. Is it best to stick with idiomatic JavaScript?

Please pass on to anyone who would have constructive input.

@DrBoolean
Copy link

I for one, think this is terrific code.

There is a considerable amount of boilerplate as there would be if we were in 1988 and everything was still procedural (we'd have to wrap it all up in objects to write OO code).

90% of the gist is wrapping the File/Http api's so they are pure and functional. These will carry over from app to app though, so i don't consider them a poor use of time. I even think your transformer stack is reusable in most web applications (though an Either for errors might be good in there).

So judging readability on lines 92-96, I'd say it's brilliant.

getFileHash is the only thing that I would say to break out in to a general future based child_process fn so the logic is app specific and clear.

If you're okay with scala syntax, I'd checkout (https://gist.github.com/runarorama/a8fab38e473fafa0921d). This is an alternative to monad transformers that uses the Free Monad/interpreter pattern. That will purify everything automatically since you are essentially just generating and composing pure instructions. Alas, there is no for comprehensions so it might turn out to depend on sweet.js or something.

So I guess that's my opinion: You're plowing the road manually and the more apps people make this way, the more it will pay off. But right now we're stuck wrapping 3rd party api's to be pure so we generate lots of library code along the way. Maybe just move it out to the /lib folder to keep your application slim and clean and publish for others to reuse.

P.S. I'd love to see more pure code on npm. Just small wrappers for things like this so the effort is not siloed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment