Fogus: Node.js should become its own language
blog.fogus.me
blog.fogus.me
One of my favorite advantages of Node is in fact that it is based on javascript. When building my application I can share code between client/sever side (string formatting methods for example) and don't need to do any mental context switching when it comes to languages. I've developed in javascript for years and all that I have learned doesn't go out the window here, all I need to do is learn how to use a few libraries.
The "faults" of javascript are primarily inexperience with event looped architectures that use callbacks (people complaining about having to use callbacks and the spaghetti code messes they make), people remembering the past horrible browser implementations of it (where something would work/be fast in one browser and not another ... Internet Explorer I'm looking at you) and confusion over how prototypical languages work vs. standard OOP.
Callbacks aren't that bad and a few extra nesting levels shouldn't create chaos in your code. Since its based entirely off of one javascript implementation (V8) the cross browser stuff is gone. How it handles OOP is different, just watch these videos first and you'll be fine: http://www.yuiblog.com/blog/2007/01/24/video-crockford-tjpl/
As far as speed goes it gets huge benefits by being javascript. Every time the Google coding machine feels like making V8 a little faster, Node gets faster too.
Yes its different, no its not worse ... its better but takes some getting used to.
Disclaimer: I don't program in JavaScript or Node.js. So I'm talking from conceptual understanding, not experience.
It would be a good idea to write some JavaScript before you make statements like these with so much conviction.
Maybe for a lot of people this is true, but for me the faults of javascript are ingrained language "features". Actually, I'm not the only one who thinks so, because theres a website dedicated to it[1].
My point is that any language fault can be memorized and worked around, but that doesn't make it go away. Also, I've noticed a lot of non-experts using javascript, so if the obvious solution isn't the correct one (using == vs using === for example), then that does cause real problems.
Note that I'm not saying Javascript is an especially bad language, just that it has, in my opinion, some severe faults.
Off the top of my head, I'd say because "var result = long_io_operation(req);" is not only not self-documenting but actively deceptive if long_io_operation is an asynchronous call.
I'm not sure I see what's to be gained by pretending closures don't exist.
If someone took v8, added lightweight coroutines, and wrote a preemptive scheduler for it, I bet you could massively reduce the SLOC count for large node.js apps.
That may be so, but since Javascript is inherently single threaded _and_ this is about javascript I agree with Cushman that it is indeed deceptive.
Also, the reason you care about the "synchronicity" in the first place is because your environment forces you to care! When it all Just Works (TM), you don't care whether it's "synchronous" or not. That's a deficiency in your language, not a feature.
I'm not theorizing. I program in this sort of language all the time. You worry much more simply about how long something will take, which you can never not worry about, rather than how many bits are "synchronous".
Apparently the plan is to implement this "theoretical replacement language" in javascript using node.js
Is that in the language standard, or are you confusing details of implementations for the language's design?
d = long_io_operation()
d.addCallback(callback)
d.addErrback(errback)
With defer.inlineCallbacks: try:
result = yield long_io_operation()
callback(result)
except Exception, e:
errback(e)
It is not necessary to use functions in the second case. yield f1()
yield f2()
You force f1 to finish before being able to start f2, which goes against the whole point of using something like twisted in the first place ! I have also noticed that inlineCallback screw up the traceback (e.g. http://twistedmatrix.com/trac/ticket/3622 - I still see this with twisted 10.0.0).While I am no genius, I am a decent programmer, with quite a few years of experience in python, and I still can't read most non trivial async code in twisted with confidence. It is almost impossible IMO to get a clear idea of the codeflows and especially error flows to ensure error handling is done right. Error handling is already difficult as is, adding exception made it that much harder, but adding async makes it basically impossible except for Vulcans.
inlineCallbacks allows you to write code in a synchronous manner (that's the point). If you'd like to wait for completion of both f1 and f2 functions simultaneously you could:
yield DeferredList([f1(), f2()])
The ticket you mentioned is closed as invalid. Use `failure.printTraceback()`.Non trivial async code is hard to read (period).
Exceptions allows you deal with errors in the place where you know what to do with them, not in the place where they arise (as in any other Python code).
Using deferred lists is a fair point, but then you are kind of back to using deferred directly.
As for tracebacks, I used the ticket as an example: if you have to use failure.printTraceback, it means you caught the exception, which means you need to catch them everywhere. But the problem is when there is a big in your application because you forget to handle an exception - debugging those is a PITA because you don't get the right traceback.
So yeah, async code is hard, inlineCallback sometimes helps, but all this really feel like a big kludge to me - I would rather use a framework where async is abstracted away. Doing all this by hand does not work very well for complex applications.
Starting from scratch is going to be difficult at this point; by the time you even have a half-decent stack you're going to be way behind.
(At the very least... you do realize this has been done and you don't have to start from scratch, right? Rather a lot of Node.js users have exactly the cognitive holes I'd expect from not knowing that Node.js is at best "keeping up", rather than being anything like cutting edge. And I'd say this style of programming was actually on its way to the dust heap of history before being brushed off and gotten a shiny new coat of paint sprayed on... it really doesn't work very well....)
I laid it out more at http://news.ycombinator.com/item?id=2370303 .
Javascript doesn't particularly bring any abstraction capabilities to the table that Perl (POE, Event::Lib, IO::Async), Python (Twisted), or Ruby (EventMachine) doesn't, and current Javascript is actually deficient in the abstraction department by comparison. (The latest ECMAscript spec can compare to them, so Javascript will get there. Something like Python generators allow you to string together a couple more tricks together before the complexity becomes a problem, and Javascript will eventually have this.) They all result in programs that end up the same way. There are some tricks you can play that improve the situation a bit over raw callbacks, like trying to sequence them, but composing these tricks together gets harder and harder the fancier you get.
None of those libraries really took off and some of them I know had way more effort poured into them than Node.js has seen yet, like Twisted. And it's because the style, combined with language runtimes that can't manage the scheduling for you while retaining the code context, has certain inevitable results. You can (and should!) delay the inevitable, but it's still inevitable.
At the very least, I would suggest that if you going to consider using Node.js you should be aware of the fact that it's not a blindingly new thing, it's a relatively well-explored space being translated into a new language, and you can get a good sense of the tradeoffs from the many previous explorations of this space. There are still places where it may be the right choice; as I said in that other message, I have chosen event-based coding in another context because it was still the right choice. But I knew the tradeoffs going in, and took steps to mitigate them, rather than being blindsided by them.
You don't need a project like Beam.js to do that, but having someone else having already written the utility code can certainly be useful. Nothing like that existed for Perl but it hasn't been hard to do what I needed.
"way out" belongs to "it", so you're looking for the possessive form. English pronoun possessives do not use apostrophes (whose, mine, his, her, their, its), while everything else DOES (the dog's, Steve's, Portugal's, the crowd's).
tl;dr: it's "its".
Last expression in a function is returned. This includes assignment statements which are in fact expressions.
foo = () ->
a = bar()
z = () ->
if a
b = fu()
else
b = kar()
Here, foo returns the function z, and z returns either fu() or kar() while assigning the value to bOne could argue this is just syntax but I think this way beyond the kind of thing that comes to mind when somebody thinks of "syntactic sugar".
foo = ->
a = bar()
z = ->
if a
b = fu()
else
b = kar()Apart from pleasant syntax, coffeescript also improves upon semantics where it can - this binding, lexical scoping etc etc. People are willing to adopt Coffeescript because it compiles to javascript, it has improved semantics and clean & mostly familiar syntax.
To sum it up, the inertia is small when moving from JS to cofffeescript. That doesn't look like the case with funode.
That's mostly just true for browsers. Javascript itself has very few faults.
My biggest annoyances with the language are: Lack of lexical scope, no trailing commas and semicolon usage that differs from C.
I could complain all day about my annoyances with browsers (or just IE).
dosomethingasync =>
#etc
will automagically bind the closure to the current value of this.From then on, I use 'that' instead of 'this', relying on closure magic to make things work. You never have to worry about how the function is bound thereafter.
CoffeeScript is cool, as it is basically "Python in JS" but I have some resistance against languages that convert to other languages. I'm not so sure why.
foo = function(){
var bar=0
return function(){
bar=bar+1
return bar
}
}
baz=foo()
baz() // => 1
baz() // => 2
baz() // => 3
bar; // => ERROR: bar is not definedAnd nested loops that both use "i" would work.
It does work like C. The scoping you're talking about was introduced in C99.
You might expect it to alert 1..2..3..etc. It actually alerts 10..10..10..etc.
My intended point was that if Javascript had true lexical scope then each iteration of the loop would create a distinct variable. A reference to that variable would then be captured by each closure. You can simulate the behavior of lexical scoping by "abusing" closures as someone mentioned before. Like this:
for ( var i = 1; i < 10; ++i ) ( function( i ) { setTimeout( function( ) { alert( i ) }, i * 250 ) } )( i )
This reminds me of bug in the Visual C++ 6.0 compiler, where a variable declared in the "head" of a for loop would continue to exist after the loop scope had closed. Javascript has exactly the same problem, but by design, due to only having global and function-level scope.
I wonder about the word "abusing" here. Languages that have block-level lexical scoping (I hope I am using the correct word) sometimes implement block scoping by renaming the variables and hoisting them to the enclosing function, and they sometimes implement block scoping by creating a new closure just as we do by hand in JavaScript, and then hoisting the result.
For example, naïve implementations of Scheme give you a "let" macro that is expanded into the closure form you give above, and then an optimizing step comes along a little later and performs lambda hoisting for you. So this "abusing" we are doing is what the language would have done for us in certain implementations anyways!
for (var i = 1; i < 10; ++i)
{
with({i:i}) // let (i=i)
{
setTimeout( function( ) { alert( i ) }, i * 250 );
}
}
will do what you intend. If let is available, for (let i = 1; etc. will do the same thing without the cruft.EDIT: http://pastebin.com/7BUXKVXT
Paste it into Racket and choose language R5RS.
(define-syntax for
(syntax-rules ()
((for ((var init) condition step) expr ...)
(let ((var init))
(let loop ()
(if condition
(begin
(begin expr ...)
step
(loop))))))))
(define (test-for)
(let ((v (make-vector 10)))
(for ((i 0) (< i 10) (set! i (+ 1 i)))
(vector-set! v i (lambda () i)))
(for ((i 0) (< i 10) (set! i (+ 1 i)))
(display ((vector-ref v i)))
(newline))))It would be the same variable with or without lexical scope (by that I mean block-level).
Personally, I would like to be able to choose the capture method, but having value capture the default. Most of my code simply wants the value, but occasionally, capturing the variable is needed.
Having coded JavaScript professionally and enjoyed it thoroughly for 6+ years now, I respectfully degree. The Harmony project shows just how much work there is to be done to make JS more fun/practical to use day in and day out.
Javascript itself has very few faults.
We disagree.Signed,
Douglas Crockford, Jeremy Ashkenas, and Brendan Eich
This allows you to trade off all the cool speed of V8, but running node.js singlethreaded, for running on an alpha port of V8 for the Beam VM. If you want to sidestep the problems with Node.js, but keep the good parts, this might be a way forward. Additionally, JS on the Beam VM can seamlessly interact with any other Erlang code, so you get all kinds of other benefits that are erlangy.
For me, more interesting than a pure assault on callbacks would be to see if you could handle more asynchronous scenarios, using async.js as a baseline.
e.g. callback waterfall, callback when all children are finished, callback when any children are finished
What's so hard about this to get a sequential execution of functions/blocks, error'ing out on the first error:
async.waterfall(
[ function (callback) {
// do thing 1
callback();
}
, function (callback) {
// do thing 2
callback();
}
, function (callback) {
// do thing 3
callback();
}
]
, function (error) {
if (error) {
// ohnoes errors
}
// all done!
}
);The original from the article
function do_request(req) {
return long_io_operation(
req,
callback=function(results) {
return create_something_from(results);
});
}
How it might be written if you saw it in a codebase function do_request(req, callback) {
long_io_operation(req, function(err, results) {
callback(err, create_something_from(results));
});
}
It seems like the article is not finished, so I can understand that there might be questionable sections. Also you'll note that he forgot the `error, results` argument pattern for callbacks, making me think that he is not terribly familiar with node.dosomethingasync(f() { });
With a language so dependent on callbacks, anonymous function are so verbose.
dosomethingasync ->
#etcThe Javascript that can be used only via add-ons is not the eternal Javascript...
Edit: http://haxe.org/ is based on AS3