Sometimes, fundamental switches in thinking are worth it. :)
For example, continuations would be perfect here, and are a wonderful tool for simplifying "nested callback hell" type problems.
But why should a web developer care about continuations?
Consider Paul Graham's Arc challenge: Build a web page that presents a form to the user. After they submit the form, a new page presents them with a link. The link takes them to a third page where the submitted value is presented back to the user. The restriction is that the input field must not be passed in via the URL.
Right now, the user must write in "continuation-passing style", which means threading callbacks, like this:
function serve(req,res) {
askUserForValue(req,res, function(value){
showLink(req, res, function(){
render(res, {"user-value": value});
});
});
}
Or perhaps the user could somehow stuff the variable into a "session" library with three separate URL routes: function serve(req,res) {
render the form with the POST action of /submit;
}
function submit(req,res, value) {
req.session["uservalue"] = value;
render the link that sends the user to /view;
}
function view(req,res) {
render the value of req.session["uservaule"];
}
This one is even more fragmented since you have three separate functions that model one user interaction flow! I certainly like it less than the callback example -- at least I can keep one in my head.We can do better though. Continuations let you write this:
function serve(req, res) {
var value = askUserForValue();
showLink();
render(res, {"user-value": value});
}
If node.js supported continuations, the askUserForValue() function would respond with the form and suspend the serve function right then and there. After the user made a new HTTP call, the suspended askUserForValue() call would return to serve() and its return value would be placed into our variable. Then, the showLink() function could render the link and suspend the function again. Once the user makes another HTTP request, the function resumes, and so on.The server would have to store the state of the computation itself to disk so it could continue later.
With continuations, we can pretend the user's web browser is the program counter. This frees my mind up. First, I don't have to keep callbacks in my head since there aren't any callbacks anymore. Second, stack traces work the way I expect because functions explicitly call each other. Third, if askForValue() throws an exception if the user inputs a string where I entered a number, no problem! Just wrap it in a try{} block that asks the user to enter a valid value, as if I'm writing a text program that calls readline().
Also note that this is different from just letting each user interaction be in its own thread! Consider:
1. Continuations should be serializable to disk. You wouldn't want blocked threads to just pile up as more users visit your website, and if your server crashes, you certainly wouldn't want to forget their state.
2. The browser "back" button would have to step a program backwards in time; something that is unsupported in a naïve thread-equals-user-interaction system. (E.g. how many of you have booked an airplane ticket with Travelocity/Expedia/whatever, pressed "Back", and then got a big error because the server didn't know how to step backwards through its user stack?) But the ability to save continuations to disk is the ability to save the current state of the program to disk, which makes stepping back in time easy: you can simply reload what was about to happen, just like savestates in a video game emulator.
This is not a new idea. Many Common Lisp and Smalltack web frameworks work well with continuations and have done so for years (the first Seaside release was 2002, I'm not kidding you!)
Racket, a great dialect of Scheme, ships with such a web framework out of the box. Here's my implementation of the Arc challenge in Racket. Note how the 'start' function is implemented: https://gist.github.com/gcr/ad116f565e105c8b2e0d
It's super easy to try this out and you don't need to fiddle with dependencies or anything. Just download Racket from http://racket-lang.org/ , copy+paste the code into DrRacket's giant text box on the top, press Run, and your web browser will pop up with the example.
askUserForValue
.then(value => res.showLink()
.then(_ => res.render(res, {"user-value": value}))
Infact I think something like this could probably be built with monadic observables and a nice library. The only thing you couldn't do is automagically keep intermediate state anywhere else other than memory :/The thing is, isn't this just a pipe dream? The moment you need to interact with an external system, like say an SQL database, you can say good-bye to rewinding, or be fine with the fact that you might commit a transaction twice, or otherwise come up with a complex method to also rewind the database state, or write your own database that supports this :)
Edit: Also, these days this approach is largely unnecessary. Now the entire thing would work on the client and the server would only be responsible to provide an API :P
But sure, there are limitations of course. :)
I mean, invalidating of the "observable" chain could also be done in JS, but I don't think that I know of other languages that could automatically transfer the state to the client.
Ruby(!) supports them, so it might be possible for Rails to create something like the pattern I mentioned. In fact, Ruby people use continuations for a few things like the built-in Generator class and making "restartable exceptions" so the thrower can figure out how to deal with the problem rather than the catcher, which is sometimes quite useful: http://chneukirchen.org/blog/archive/2005/03/restartable-exc...
Properly implementing continuations is a huge implementiation burden though, which is another reason why the're uncommon. The language would have to make the stack frame itself slicable and serializable, and it's hard to get good performance when the call stack itself becomes a "first class" language construct. Consider a duplicate() function that saves the state of a program and then spawns ten threads that each reload that state! Have fun implementing continuations in a way that supports that, Guido! :)
Serializable continuations can be saved to disk. You can reinstate a completed continuation (eg. go back from the final page. After clicking the link again, the page completes again. Or try clicking the link in several tabs; the page concurrently completes several times as you expect.) Generators make this pattern impossible; Continuations give it to you for free.
Second, by being saved on disk, they aren't crammed up in the event loop so they aren't a RAM leak. Racket even provides a way of having the client store all state if you wish; such "stateless" continuations have no memory or disk overhead on a long-running server and never expire until you change your site's source code.
Continuations are useful when you're waiting for the user to finish doing something (eg. an HTTP response, not a request).
So they do solve different problems.
Yes, JS was a rushed language that was then left to rot for 15-odd years. Unfortunately it's the language that powers the web, runs everywhere, and (along with HTML/CSS) is the only thing that all the big guys agree on.
It's getting better but it is what it is.
Those are excellent reasons to use javascript to power your web applications client side as opposed to say Flash or ActiveX.
However, those have absolutely no bearing on writing your server code in JS.
The only reason to run JS on your server is so you can write the server side and client side in the same language. That's a debatable reason for running JS on your server.
Node incredible scalability around IO is also provided by a host of other not rushed and left to rot languages. Erlang, Go, C++, even Java has libraries for it.
When it comes to server side you can make a pretty good argument that the "emperor has no clothes".
I do agree with you that being able to share code between client and server is a poor argument. It is a rare case in my experience where anything but the most trivial JS code can be shared between client and server.
If I am building an HTTP(S) API server that is I/O heavy
(which many are), I will pick Node every time until
something else comes along to replace it.
This is exactly what I'm talking about. There were languages/frameworks that beat node already out there before Node came along. It mystifies me why people think there weren't. Again the only motivation that actually makes any kind of sense for creating Node is shared code between client and server.It isn't about using Node because it is cool or I like Javascript (which I hate BTW). It is about accomplishing my goal of an efficient HTTP API server much more quickly with Node than any of the other languages/frameworks that have come before. In my opinion, this is the niche Node does the best and if I have the choice I will choose it until another language/framework comes along to beat it.
e.g. http://twistedmatrix.com/documents/10.1.0/core/howto/threadi...
not really much boilerplate...
Node (and Javascript) is definitely missing proper support for threads which is a negative.
It gets lots of stuff done, so I'd say it's pretty well-clothed.
I guess there's something to be said for not having to learn more than one language. Probably something vulgar.
Those are minimum reqs for a language and community. They aren't differentiators. Aside from their absence being a negative indicator.
There are only a few high-quality-working-and-well-documented modules.
1: http://en.wikipedia.org/wiki/ECMAScript#ECMAScript_Harmony_....
https://github.com/rwaldron/tc39-notes/blob/48c5d285bf8bf0c4...
(This is kind of the whole point of monads - you could write a piece of code by composing a few functions, and then make it asynchronous without breaking the function composition! No need to rewrite your code to use callbacks; the same code will just work.)
Javascript has none of the following:
- Macros
- Monads
- call/cc
In fact, most of it was and is written in C/C++, horror of horrors, and fewer in Pascal / Java.
The notable exceptions I can think of are Emacs (partly), Autocad (partly) and Pandoc.
So I guess there's a tradeoff, use cool languages and not contribute much of usable software, or use some so-so language and create stuff people use.
But I don't want to go down the rabbit hole of saying that "TeX & Emacs are somehow more powerful than Chrome because their systems use macros."
Granted, sure. People cope well with not being able to express their problems the way they want to. The existence of so much high-quality C/C++/blub software proves this fact.
But for those of us who don't want to change our thinking, tools that fundamentally extend the language are great to have in our toolbox. Maybe it's our weakness, that we feel uncomfortable when we can't express the problem the way we want. Or maybe it's our strength. I don't know.
True, had TeX itself in mind!
Clojurescript's core.async is a macro approach which is not quite the same but very similar to what I describe.
You could probably do it in sweet.js (marcos for javascript that are expanded at compile time) but as far as I know, nobody is actually doing it so there are probably showstoppers.
A monadic approach requires all of your code to be pure-functional, which is unreasonable because you'd have to rewrite all of your dependencies to be pure.
For monads to work, all your code needs to be pure (in the FP sense). So if you want to do anything substantial with monads in an imperative language, you need to rewrite the entire ecosystem of libraries. That would cost quite a bit more money than your average organization can afford ;)
Lack of purity is a defect which bites you whether or not you have monads: so if you're perfectly happy dealing with it in your existing code base (like most of us are), making explicit the “monadic” patterns in your codebase will not make it worse, and may improve things a bit.
are callbacks vs mutexes vs actors vs STM the best way to handle concurrency, that's debatable. but what's not debatable is that comparing concurrent vs non-concurrent code the latter is always easier for people to write and reason about, so you should tend to want to work in environments that are non-concurrent by default and let you focus just on the parts that you need to deal with concurrency.
By the way, one of the major benefits of Node is that it allows you to use the same libraries in your server and web-applications. So even without the scalability concerns, there would be good reasons to go with Node.
The Node environment seems to have shifted from "use this for your one-off, high performance, I/O dominated real time service" to "hey, you know Javascript, build all your webapps in node, build your command line tools in node, build everything in node, since it's the lingua franca of the web." This latter trend sucks, and results in things that to me seem absolutely bonkers, like the Vows test framework.
Oh, that also means that this given system has to reach critical mass. Oh, and since everything's server and client side, the compilation has to be rock solid and be easy enough to understand without reverting to javascript in frustration.
I miss programming in Node, for nothing else that I hate having to keep up with so many paradigms and switch between the two. I only have X amount of hours to keep up. When I move back to heavy development in a language after even a few days off, there is friction.
And as someone who writes both front end and back end code, it gets frustrating. I'm not as good as I could be, given the hours in the day I have.
Example: https://github.com/spion/async-compare/blob/master/examples/...
With promises, generators are not really necessary -- especially if you add arrow function syntax into the mix. https://gist.github.com/spion/8254661
(Note: that particular method in doxbee has long since been refactored to something saner with better separation of concerns :)
The naked Emperor JavaScript runs everywhere, and runs fast, and isn't going away. He's streaking at lightning speed all over the kingdom, everybody's seen him, and he doesn't stay still.
The emperor is naked. Long run the emperor.
https://github.com/laverdet/node-fibers
This is what Meteor uses on the server side, and it's a pleasure. Everything is suddenly synchronous.
Ideally, a language ought to have one representation for lazy evaluation, not a dozen; perhaps at some point a new standard will come out that standardizes some subset of lazy evaluation that has won out. Welcome to Javascript.
But Javascript does not have a builtin concept of threads.
We could even give a name to these sequences of operations... maybe call them "strands"... or "filaments"...
Using libraries in Node.js seems to be a matter of 1) finding a library, 2) taking a hammer and smashing it into your existing choice of continuation-tracking mechanism. We end up with N x M different ways of using a package, depending on what style the package author chose vs what style the programmer chooses. "nodeify" is a bandage on top of a missing limb.
Do people really put up with this? Just look at how many different things were benchmarked in this article---I counted 21. How many more ways of reinventing basic programming-language design is the JS community going to come up with before we realize that the emperor has no clothes?