5 ms·
CoffeeScript, a little language that compiles to JavaScript. Happy Holidays, HN
- jashkenas 17y agoJavaScript has always had a gorgeous object model hidden within Java-esque syntax. CoffeeScript is an attempt to expose the good parts of JavaScript through syntax that favors expressions over statements, cuts down on punctuation noise, and provides pretty function literals. This CoffeeScript: 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.
- messel 17y agoLooks great, I'll need time to dig in but Thank You in advance
- j_baker 17y agoAlright, this is bugging me. Why are you using colons for assignment?
- jrockway 17y agoI am not the author, but I assume because it's nice-looking, and JavaScript already uses it for assignment (inconsistently). Consider: var foo = 42; { foo: 42 } Why make the two concepts different? Also, it's "binding", not assignment.
- jashkenas 17y agoa: 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)
- nir 17y agoAny chance you'll allow equal sign as well as colon? For all of us coders conditioned by years of programming to automatically use "="... Looks beautiful, in any case.
- j_baker 17y agoErm... no, it's assignment. At least according to the documentation: http://jashkenas.github.com/coffee-script/#assignment http://jashkenas.github.com/coffee-script/#assignment At any rate, I was just curious. To me, x = 1 looks more intuitive than: x: 1 But that's just my personal preference.
- karanbhangui 17y agoprobably irrelevant, but very pleasing design on that page :D
- cscotta 17y agoWow, this is a really nice little language. 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.
- jashkenas 17y agoI completely agree -- I'm not about to start using it for real projects, it's really just a thought experiment about how nice JavaScript could be with an alternative syntax. 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.
- mbenjaminsmith 17y agoCool. I've been dreaming of writing a similar python => javascript (which someone has probably done already). 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.
- JeffJenkins 17y agoYou're looking for this: http://pyjs.org/ http://pyjs.org/ 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 http://github.com/jeffjenkins/py2js
- leif 17y agoBeautiful! (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.
- jashkenas 17y agoYeah, the periods to end block scope are the part of the syntax I'm the least happy with. I couldn't think of anything nicer without resorting to significant whitespace (which I'd like to avoid). 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.
- tdavis 17y agoOut of curiosity, what makes you want to avoid significant whitespace? I think it solves the issue quite nicely. For what it's worth, I used to think SW was completely absurd. Then I spent a few years actually writing Python and have no desire to ever go back.
- j_baker 17y agoPython's requirement of indentation is somewhat limiting in terms of what the language can do. For instance, that's partly the reason why Python doesn't allow more than a single expression in a lambda.
- jashkenas 17y agoIt doesn't? Is there some kind of documentation about it we can look at?
- collint 17y agoI like the look of this. I'm excited about the possibilities of languages that target JavaScript. It's quite a shame that the "open" web has only one language.
- wensing 17y agoThat array comprehension feature is hot.
- extension 17y agoSplendid! This is what JS really needs, a thin layer to clean up the syntax and patch the quirks, without breaking anything. I made the same kind of thing using the actual Ruby parser: http://github.com/jedediah/prettyscript http://github.com/jedediah/prettyscript It's only barely usable at this point and I've been too busy to work on it.
- jashkenas 17y agoGreat stuff. The one really interesting overlap between the two -- from taking a quick glance at your readme, is that we're both trying to convert things that otherwise would be statements in JavaScript into expressions. 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 http://jashkenas.github.com/coffee-script/#expressions How are you doing it? Did you find any particularly tricky cases?
- extension 17y agoThat's pretty much what I did. There is a polymorphic method to_js_tail on AST nodes that asks the node to emit itself as a return statement. Nodes that are values just prepend "return", blocks forward the call to their last statement, control structures forward to each of their blocks and so on. 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.
- jcl 17y agoHeh... Might want to choose a file extension other than ".cs".
- ComputerGuru 17y agoExplanation: CS is the C# extension. At least, I think that's the parent's point...
- jashkenas 17y agoYeah, all the good extensions are taken -- .cof and .cfs are already other things as well. None of the tooling in the compiler cares about what extension you use: you can call it .coffeescript if you'd like. I'm going to stick to .cs for the parallel to .js for the time being.
- timdorr 17y agoWell, it seems that .cof and .cfs aren't associated with other programming languages, so the overlap there is less problematic.
- jcl 17y agoI think the biggest issue is that it makes it hard for programming environments to pick the right syntax coloring. There may also be problems selecting the MIME type when serving the file. 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.
- jeremyw 17y ago.coffee seems an appropriate balance between overloading the namespace and a too-long common string in one's directory. The 2- and 3-letter extension tyranny must stopped!
- jashkenas 17y agoNice idea. I've changed the official extension from ".cs" to ".coffee", and it'll go out with the next release. Thanks.
- amix 17y agoCoffeeScript + node.js + V8 could produce one amazing language and implementation. Great job on the syntax, I think it's cleaner and more readable than JavaScript.
- tlrobinson 17y agoIt already works on Narwhal (http://narwhaljs.org http://narwhaljs.org). See http://news.ycombinator.com/item?id=1014508 http://news.ycombinator.com/item?id=1014508
- jrockway 17y agoThis is nice. There are too many gotchas to remember in JavaScript; better to let a liberal compiler translate something nice into the "machine code" that works in all browsers. (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.)
- btilly 17y agoIf the second form works and avoids bugs, can't you train your eyes to accept it? That's what I did in SQL a few years ago, and I can't count how many times it has helped me since.
- jashkenas 17y agoWhich is why CoffeeScript lets you leave commas out of multiline object and array literals (for those that didn't see catch it in there): something: { foo: "bar" bar: "baz" }
- aboodman 17y agoThis is really cool. Nice work.
- tlrobinson 17y agoVery cool. I've created a simple CommonJS package that lets you use CoffeeScript on the command line with Narwhal: http://github.com/tlrobinson/coffee-script http://github.com/tlrobinson/coffee-script If you have Narwhal (http://narwhaljs.org/ 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.
- jashkenas 17y agoOh wow. A CoffeeScript REPL? If it can hook right in to the Narwhal standard library, that'll be quite a thing to see. 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.
- tlrobinson 17y agoIndeed it can use all the standard library modules, since the normal "require" functions are available. 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.
- jashkenas 17y agoAlright -- this is fixed with the latest commit. I've enhanced the --no-wrap option to not emit "var" declarations at the top level. If you don't want the safety wrapper, then you probably intend to make them global in any case. 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.
- tolmasky 17y agoI believe there is a bug in your object model for handling super. I have tested it and it does indeed work incorrectly (although not in the way I expected it to work incorrectly). Basically, your super calls only work if your class tree is 1 level deep. As soon as you have something like Animal -> Horse -> BigHorse, then it breaks down. This is because super() gets translated to: 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.
- jashkenas 17y agoThanks for that. You're absolutely right -- the javascript dynamically-scoped "this" keyword strikes again. I've fixed it in the last commit, by using prototype.__proto__, and adding an "extends" keyword for convenience. You can see it in action here: http://jashkenas.github.com/coffee-script/#inheritance http://jashkenas.github.com/coffee-script/#inheritance It's out with version 0.1.2 now.
- tlrobinson 17y agoBe careful with __proto__, it's non-standard and will break in some engines.
- vr 17y agoTo get the equivalent of __proto__ that works in every engine see goog.inherits from Closure.
- tlrobinson 17y agoHow do you do object literals with non-indentifier characters in property names? (e.x. { "foo+bar" : "baz" } in JS)
- jashkenas 17y agoYou couldn't, a minute ago. I've just pushed an update to the grammar that allows you to use quoted strings inside of object literals. Thanks for catching it. I really appreciate folks taking the time to find the little missing pieces.
- wmoxam 17y agoVery cool, the syntax is similar to Potion CoffeeScript: square: x => x * x. Potion: square = (x): x * y.
- jashkenas 17y agoCoffeeScript's now up to 0.1.3, which includes --interactive to launch a CoffeeScript REPL, and --run to compile and execute scripts in a single pass. Both options depend on a working Narwhal installation.
- Schtarflucz 17y agoSweet! 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 ;-)
- deleted 17y ago[deleted]
- jashkenas 17y agoYeah, I think you're right about the other operators -- I've just pushed a commit that puts them back to +=, -=, ||=, and so on. Thanks. Underscore is going to stay unrelated, but there's no reason that you couldn't use it from CoffeeScript, to great effect.
- Daishiman 17y agoLooks great! Does it work with jQuery?
- statictype 17y agoVery nice. How about language-level support for asynchronous operations (like the async monad in F#) 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); ); }