CoffeeScript, a little language that compiles to JavaScript. Happy Holidays, HN
jashkenas.github.com
jashkenas.github.com
square: x => x * x.
Compiles into this JavaScript: var square = function(x) {
return x * x;
};
If anyone has specific ideas about aspects of JavaScript that they think could be more convenient or better-looking, I'd love to hear them. Cheers. var foo = 42;
{ foo: 42 }
Why make the two concepts different?Also, it's "binding", not assignment.
a: 1
b: 2
obj: {
a: 1
b: 2
}
Quite right, and internally, both kinds of assignment compile into a CoffeeScript AssignNode. The latter is just tagged as occurring within an object literal. Having the commas be optional in multiline objects also helps make both kinds of assignment look identical.(And I'm calling it assignment because saying "binding" in JavaScript usually means that you're binding a function to a "this" context object)
Looks beautiful, in any case.
At any rate, I was just curious. To me,
x = 1
looks more intuitive than: x: 1
But that's just my personal preference.I've created a simple CommonJS package that lets you use CoffeeScript on the command line with Narwhal: http://github.com/tlrobinson/coffee-script
If you have Narwhal (http://narwhaljs.org/) just install with the package manager "tusk install coffee-script", then you can create Narwhal modules ending in .cs, or run the command line REPL "cs"
(there are a few problems, such as variables in the REPL are created in a local scope, etc, but this was literally a 15 minute hack, I'll improve it when I have time, or feel free to submit patches)
I'm starting to think of Narwhal as less of a JavaScript platform and more of a VM akin to the JVM... JSVM? It now supports Objective-J ("tusk install objective-j" or "cappuccino"), a proof of concept Ruby implementation ("tusk install cappruby"), and now CoffeeScript.
Looking briefly at your commits, I'll add a flag to disable the function safety wrapper so that you don't need to strip it off. Thanks for the tusk package -- it's quite a Christmas present.
The other problem with the REPL is that all variable assignments are prefixed with "var", so they're always local to the function where the eval-ing is done, thus it's impossible to get variables to persist between REPL commands (without explicitly assigning to a property of "global").
One solution would be to add a flag that disables the "var" for top level assignments (but not within functions).
Alternatively, I think I could do some engine-specific black magic (__proto__ hacks), at least for Rhino.
bin/cs now works like a charm. I might pull it back into the main bin/coffee-script executable. Start a REPL if run without arguments ... --run for executing CoffeeScripts via Narwhal... something like that.
this.constructor.prototype.NAME.call(this, args)
So, if we had move on BigHorse, Horse, and Animal, BigHorse would call super (resulting in Horse's implementation being called), but then Horse calls super which again calls Horse's implementation, since this.constructor.prototype still points to Horse.prototype. Thus, you can't continue climbing up the chain. When I tried it though, it seemed to just skip the intermediate classes and go down to the base class, which is of course equally broken. We used to have the same problem a long time ago in Objective-J. The fix is pretty simple, you should just inline the actual class name when dealing with supers:
Horse.prototype.NAME.call(this, args), which then calls: Animal.prototype.NAME.call(this, args)
Hope I avoided you some pain with that one. I remember 3 years ago not understanding why on earth my code was broken and thinking it was algorithmic for the longest time until I realized the entire underlying parts were broken.
http://jashkenas.github.com/coffee-script/#inheritance
It's out with version 0.1.2 now.
CoffeeScript:
Horse extends Animal
JavaScript: Horse.__superClass__ = Animal.prototype;
Horse.prototype = new Animal();
Horse.prototype.constructor = Horse;
Calling super uses the __superClass__ reference. I hate to add it as an enumerable property, but there doesn't seem to be any other way to get at it, and at least it's on the class and not the object.The howto refreshing to read. While I love JS as a language, even written longhand, it feels like you've brought an uncommon elegance to it. The syntax is much simpler and maps more cleanly to how I think. It feels a lot like Ruby.
Granted, most of the JS I work on is in pretty complex applications, so I'd be hesitant to work in anything but the target language itself. But it's a very clean abstraction or concept to begin with. I'd love to see something complex written in CoffeeScript, especially with DOM manipulation or another JS framework involved.
It'd be fun to try writing a DSL or simple compiler sometime. This must have been very interesting to create. Cheers for such detailed examples in the unveiling as well.
Great job, and thanks again!
Edit: Hahaha, thanks for the examples from the Poignant Guide. Pouring out a few drops for _why right now.
Edit 2: Forgot that you wrote underscore.js - thanks for that as well. Crazy to see how simple it is when written in CS.
That said, if you work with Ruby and want to play with compilers, check out CoffeeScript's source. It's got a clean Ruby lexer and Racc parser (examples of which are hard to find in the wild) -- the code generation is a little funky, but I'm hoping to clean that up. The whole shebang is under 1500 LOC, including comments, so it's not too much to wrap your head around.
async update_table:q =>
r1,err = xhr('http://somewhere?q='+q)
if err then
$('error').innerHTML = err
return.
r2,err = xhr('http://somewhere-else?q='+r1)
if err then
$('error').innerHTML = err
return.
$('result').innerHTML = r2
.
could be the equivalent of something like: function update_table(q) {
var on_error = function(err) { $('error').innerHTML = err; }
doXHR('http://somewhere?q='+q
,function(r1){
doXHR('http://somewhere-else?q='+r1,function(r2){
$('result').innerHTML = r2;
},err));
},err);
);
}http://github.com/jedediah/prettyscript
It's only barely usable at this point and I've been too busy to work on it.
CoffeeScript does this by using the AST to recursively push down returns and assignments requested from outside a block to the final line of each possible branch of execution:
http://jashkenas.github.com/coffee-script/#expressions
How are you doing it? Did you find any particularly tricky cases?
That just handles returns. I never implemented statements as expressions in general. I was planning to do that by wrapping non-expressions in anonymous functions but if you can do it by injecting temp variables, that's probably a lot more efficient.
(Personally, I always write:
something = {
foo: "bar",
bar: "baz",
}
which works in Firefox, but not IE. A compiler would not be upset when it omits that extra trailing comma. Of course, you can always write: something = { foo: "bar"
, bar: "baz"
}
But let's face it, that is only not ugly in Haskell.)That's what I did in SQL a few years ago, and I can't count how many times it has helped me since.
something: {
foo: "bar"
bar: "baz"
}(I finally made it to the end of the article where you ask about block delimiting...)
I think there are two issues, one being that '........' at the end of a large nested expression is ugly, the other being that it's hard to find the start and end of a block as it stands.
Since you seem to borrow a lot from ruby, why not add {} and/or begin/end as options? I would definitely keep the period syntax around (for short, shallow blocks), rather than replacing it.
"aint"? Cute.
I'd rather not add {/}, or begin/end, because it's easy to determine the beginning of the block, it's just closing it that we need a symbol for.
In terms of style, you can certainly indent the period on it's own line if you prefer:
elements.each(el =>
$(el).click(event =>
if el.hidden
el.highlight()
.
.)
.)
But I'd love to hear more suggestions for alternatives.Sounds good that you are going to give significant indentation a try.
Here's a question for you. In Python, if you have a couple of nested lambdas with multiline bodies, getting passed into each other as arguments, do you have to write the closing parenthesis on the correctly-indented line on the other side, or can it be tucked against the inner lambda?
In a hypothetical significant-whitespace CoffeeScript, I'm thinking of this:
elements.each(el =>
el.click(event =>
el.show()))
Versus this: elements.each(el =>
el.click(event =>
el.show()
)
)
Is the second form required to make Python-style whitespace work?Edit: Alright, got a branch that starts with a little significant whitespace going here:
http://github.com/jashkenas/coffee-script/tree/whitespace
It can't handle the first of the two cases mentioned above just yet, but it compiles the second one correctly.
a = [
1,
2,
3]My take on the blocks is that significant whitespace seems to be a natural expression of blocks for your language and fits quite nicely with the aesthetics of the other forms. (The terminal ... choo choo train is not a winning idea.)
So if you have to use a conflicting file extension, you probably want to use one that isn't already associated with a major programming language or web file type.
The 2- and 3-letter extension tyranny must stopped!
Thanks for catching it. I really appreciate folks taking the time to find the little missing pieces.
CoffeeScript:
square: x => x * x.
Potion: square = (x): x * y.My only worry about something like this is the extra step of compiling, though I run a script that compresses via YUI comp. anyway...
Thanks for sharing.
I had written a mostly functional python->JS translator using the compiler and compiler.ast packages, but I suspect this is more complete and actively developed.
My package is here if you wanted something stand-alone (but only 80% complete) to play around with: http://github.com/jeffjenkins/py2js
Now, add a minifying pass after compilation for deployment and you'll have created a dream platform for client-side scripting (-:
edit: another suggestion since one of your goals is terseness: you may want to alias "prototype" to "proto".
edit2: as much as I like the column for asignment (present in both JS itself and Ruby 1.9), I'm not really fan of the +:, -:, etc. combined operators, at least in a math context. &&: and ||: look fine though so it's perhaps a matter of getting used to it.
edit3: I just discover your underscore.js library. Do you plan to include it in the CoffeScript standard lib?
edit4: oh... And I hope you like the color of this brand new bikeshed I just built in front of your coffe shop ;-)
Underscore is going to stay unrelated, but there's no reason that you couldn't use it from CoffeeScript, to great effect.