IcedCoffeeScript
maxtaco.github.com
maxtaco.github.com
Maybe it doesn't matter. Perhaps most users don't need it. Or perhaps as we continue to develop software we'll find that we'd rather defer work to the compiler / macros and the high cost of syntax is simply not worth it.
I'm looking forward to see how 3 languages I immensely enjoy (JavaScript, CoffeeScript, ClojureScript) co-evolve :)
What language exists in which this is not the case? Language by committee sounds terrible.
Languages which have very little "own shape" (syntax) and let you transform it easily (via macros) or provide high flexibility.
Mostly concatenative languages and Lisps, but e.g. Smalltalk or IO can probably be fit in that category as well, they have a minuscule syntactic core and that core is used to build abstractions and structures putting both the language's designers and the language's users on a level ground.
Of course that creates other issues, where every developer or organization has its own lingo and structures, making even going from one codebase to the next more expensive. It's a tradeoff.
Still, realizing that you have pretty much the same power as the language builders themselves when it comes to creating abstractions and datastructures and control flows... is an enjoyable feeling.
> Language by committee sounds terrible.
Can end horribly, and can end pretty well. Haskell was designed by a committee over ~10 years (FPCA '87 to the release of "The Haskell 98 Report" in 1997).
It does generally end baldy.
Regarding the whether it's part of the language or a library as a distribution issue, it depends on what benefits users. For syntax, there's some benefit in uniformity (other developers can read it). I'm seeing ICS as a suggestion of a new feature in CoffeeScript. If it's really good (meets a need; bugs and design mistakes are fixable and fixed; it doesn't break other things), it could be back-integrated into CoffeeScript proper. Whether to do so can be better decided once data exists. :-)
It's a great forking model for exploring new syntax, IMHO.
EDIT but it breaks the CoffeeScript philosophy of readable JavaScript, and (eg) addes debug line numbers in to the source code (see "load" on the sample code). Also, other examples and more details on ICS here https://github.com/maxtaco/coffee-script/blob/iced/iced.md
https://github.com/jashkenas/coffee-script/pull/1942#issueco...
https://github.com/mbostock/queue
I haven't tried it in production code yet, but it looks like a convenient abstraction with tunable parallelism.
https://github.com/maxtaco/coffee-script/blob/iced/iced.md (search for: "Debugging and Stack Traces")
Await these items that I want to defer.
Wait on this code block and collect these items.
As someone else said, if you avoid await/defer entirely, it seems to back off to exactly what Coffeescript does, so there is a nice progression path.
That being said, right up front he explains the two reasons why this is a departure from CoffeeScript’s core philosophy: `await` and `defer` are changes in the semantics of JavaScript, and not surprisingly, code written in IcedCoffeeScript no longer compiles into more-or-less recognizable JavaScript.
Modulo syntax and sugar, CoffeeScript is JavaScript with some minor changes to the AST (such as soaking up nils with the Elvis operator). IcedCoffeeScript is another language that evolved from JavaScript and transpiles to JavaScript.
Before adding such features willy-nilly to CoffeeScript, folks need to be comfortable walking away from a core value. I’m not saying that’s a bad thing, but it’s a problem related to the Innovator’s Dilemma: To embrace this new value, CoffeeScript will have to alienate some portion of its user base that value compiled JavaScript that is easily recognizable to anyone who has read the CoffeeScript source.
But one objection we will both encounter repeatedly is that if a language supports feature X, and you work on a team/inherit code/use libraries, there will always be people who don’t want to use feature X but wind up dealing with it any ways because someone else used it.
So the naysayers will claim that if it’s added to the core language, it’s only a matter of time before they wind up dealing with it whether they like it or not. Given the way that most Ruby users have to live with monkey-patching in Ruby whether they do it themselves or not, I fully expect that if await and defer are added to core CS, there will be libraries or CommonJS modules that use it and presto, you may find yourself looking at some of its output in the debugger one day.
I’d rather use the feature myself and benefit from it, but I can accept what the luddites are saying even if I don’t end up coming to the same conclusion about whether the feature should be added or withheld :-)
UPDATE: I recall being told that tail-call optimization should not be added to certain languages because it encourages programmers to write recursive functions that are “hard to read.” You might argue that if you never write recursive functions, why should you care? But people do care.
Confused? Here are the iterative, recursive, and itero-recursive ways of writing the factorial function:
function fact_r(N) {
return N <= 0 ? 1 : N * fact_r(N - 1);
}
function fact_i(N) {
var acc = 1, i;
for (i = 1; i <= N; i += 1) {
acc *= i;
}
return acc;
}
function fact_ir(N, acc) {
acc = acc || 1; // default value of acc
return N <= 0 ? acc : fact_ir(N - 1, N * acc);
}
So the accumulator variable becomes an argument variable which may have to be separately initialized. The key feature which makes something optimized for "tail calls" is that when f(something) wants to recurse, it returns f(something else). In other words, certain conditional logics can happen, but don't leave an operation like the "n " in "n recurse(n - 1)" on the stack or else you can't get rid of the function call. It's not even a special optimization -- basically you can move one line in the compiler a couple steps ahead of where you might have automatically written it, and it suddenly treats iteration and tail-recursion the same.So that's the difference. If you can understand fact_ir, which does the iterative idea in a recursive syntax, then you can understand tail recursion.
So this is a more fundamental fork than first appears... And it explains why it includes debugging information (loading up the code examples reveals artifacts like lineno: 6 in the output JavaScript).
I recall a debate about debug info, turning on the values you note. Can't find it right now. (though, apparently extra-linguistic Source Maps are being introduced into browsers http://news.ycombinator.com/item?id=2862629)
Personally, I think "clean JavaScript" is the right adoption strategy, for now, in a JavaScript-dominated world. When CoffeeScript has native browser support, that value can be dropped (it might not happen: MS supports VB, Google supports Dart... even if Mozilla supports CS, the others mightn't).
Finally, I don't think the resulting JavaScript is that hard to understand - you can easily see the correspondences with the source. However, it's the thin-edge of the wedge. What changes will be added next?
There's currently an open pull request for Iced to be merged with CoffeeScript, and the merge will happen if the fork manages to:
* Provide a seamless veneer over sync patterns in async code: defer in loops, with try/catch, within the middle of an expression...
* Handle both synchronous and parallel style "maps"...
* Prove to be more pleasant / powerful to use than callbacks and promises, in practice.
* And then the hardest bit -- do it all without breaking CoffeeScript's golden rule: it has to compile into straightforward JavaScript -- i.e., the callbacks you would have written in the first place.
It's a tall order, but if Max manages to pull all that off, it'll be pretty great.
streamline.js already fits the bill, including your golden rule: the core idea was precisely to generate the callbacks that the developer would write otherwise.
Today it works with both CoffeeScript and Javascript, but in a decoupled way: the streamline transformation is applied to the JS generated by the CS compiler. It could easily be repackaged as a CS language extension.
It has been around for more than a year and the CS+streamline combination is powering at least one live site: http://www.thethingdom.com/
Bruno
While I'd love to see some of these features find their way back into the core language (or native support for coffeescript in node or browsers) it's fair to say that's a long time off if it's ever going to happen.
In the meantime, I'm getting to massively clean up the logic and syntax of my apps. Huge thanks to everyone working on this!
- I am hoping that this would be merged back if it could be somehow concluded that there isn't a much cleaner way to achieve the same result. Even if the resulting JS isn't as clean as what CS produces now. The ICS approach looks safe; they could be assured by similar approaches in more mainstream languages, most prominently C# (and F#) which have much bigger teams attacking the same problems.
If the prospect of this getting merged back looks bleak, I might consider switching ICS once I am convinced there aren't any other breaking issues.
result = await search("test")
Is this just so that it plays nicely with non-standard callback APIs? I'd prefer to introduce Promises (i.e., q) and have await interact with that. I guess this is not the NodeJS way though.defer() is needed because it becomes the placeholder for the callback function, and lets Iced know how many functions it needs to wait to call back.
I remember there was another discussion before where they first started talking about Max's implementation, but I can't seem to find it at the moment. That did seem to be the general consensus, though.
"CPS is also generally used as an IR in compilers, not something you write by hand"
http://news.ycombinator.com/item?id=3511211
This reminds me of my Celluloid::IO system in Ruby (which uses coroutines to provide a synchronous API), except Celluloid::IO lets you have as many event loops as you want:
Here's an example of using my Common Node library (that implements a number of sync APIs using fibers - http://olegp.github.com/common-node/) with CoffeeScript: https://gist.github.com/1447709
http://disnetdev.com/contracts.coffee/
It's a great way to write defensive code for those of us who find writing regular unit tests a little onerous even (if they are often necessary). I'm planning on using it in all my CoffeeScript, up to -- but not including -- generated production Javascript.
Asynchronous error handling without clutter is why I created LAEH, have a look if you want: https://github.com/ypocat/laeh2
EDIT: To clarify - an uncaught error in async handler means your Node.js is going down (unless you use the catch-all handler, which is an even poorer practice). In browser it means your callback never returns, leaving you in void (with a short an ambiguous stacktrace) to search where the problem is.
I think I can imagine a more direct translation of await & defer, though of course I haven't actually tried to implement it (yet).
I'm wanting in CoffeeScript knowledge and/or intelligence, so could someone explain the above please? (In the examples, defer seems to specify the variable that gets the result of await.)
defer is a function which returns another function. When that returned function is called, we get out of the await block.
So, to take the same example:
await
for k,i in keywords
search k, defer out[i]
Here, defer(out[i]) returns a function. We pass this function to search.Search is like this:
search = (keyword, callback) ->
# do some search
callback(some_result)
And here, by calling callback, which is the function returned by defer, it will end the await block.Here's a trivial example:
await
some_function = (fn) -> fn(1234)
some_function(defer(x))
console.log(x)
defer(x) returns a callback, it's given to some_function. And some_function calls it, which ends the await block. await $.getJSON url, defer json
Is the following right? Syntactically, "defer json" is an argument to the function "$.getJSON". So you could write it like this: await $.getJSON( url, defer(json) )
The code we are waiting for is the "$.getJSON" call. That code indicates that it is finished by calling the function returned by defer.But what does the function returned by defer do? It looks like it (somehow) sets its argument to the result... so in the above example, "json" (somehow) gets the result of the call; in your example, "x" (somehow) gets a value. I'm guessing it's some convenient compiler magic that ICS does, and not an issue of CoffeeScript syntax.
An upshot of this is you can't give defer any old argument (e.g. a numerical value) - it has to be a variable that is capable of being set.
Also, who is the "callee"? A "callee" is a function that is called... Oh, does it mean (eg) "$.getJSON" is a callee, and it calls the callback function given to it (in the jQuery docs, it's notated as "success(data, textStatus, jqXHR)"; here, that success function is the function returned by defer). I guess, given the purpose of ICS, there will always be a callee in the block - but it seems to me that, syntactically, you could just directly call the function returned by defer. Ah... but (I bet) part of the compiler magic is that you are only allowed to call the function returned by defer from within another function - this is because the result of that other function is needed in order to set the argument of defer to it. Whew.
One last thing: the description talks about "deferrals" begin "fulfilled", but it doesn't say where these defarrals come from. Now, I think they are created by calling defer.
Hmmm.... it's probably better for me to think about this in terms of lisp macros, instead of C calls, as macros allow you to do weird source code transformations, like setting the value of a variable given as an "argument". i.e. ICS is a macro.
"Is the following right? Syntactically, "defer json" is an argument to the function "$.getJSON". So you could write it like this: await $.getJSON( url, defer(json) )"
Yes, exactly.
"'m guessing it's some convenient compiler magic that ICS does"
Yes, exactly. When you say "defer(x)", it basically returns a callback like this:
function(result) {
*x = result;
Code to terminate the await block*
}
"One last thing: the description talks about "deferrals" begin "fulfilled", but it doesn't say how they are created. Now, I think they are created by calling defer."Yeah, defer returns a callback and when you call it, it "fulfills" the await, i.e. you tell await that you're done with that async block.
$.getJSON(url, __iced_deferrals.defer({
assign_fn: (function() {
return function() {
return json = arguments[0];
};
})(),
lineno: 6
}));
Presumably, when
the function that __iced_deferrals.defer returns is
called by the code in .getJSON that delivers the result, it will set arguments[0] to that result, and then call the assign_fn above. This will set json to that result.Also, I realize that it doesn't set json to the returned value of $.getJSON, as $.getJSON doesn't return anything - it uses a callback to deliver its result. It seems that ICS always works this way - which I guess implies that the use-case it's for always uses callbacks...
BTW: I'm not sure why the above has a function which is then called immediately. Maybe a coffeescript thing, for separating namespaces? I assume the lineno: 6 is for debugging - I recall some debate against this. Good to see it got in.
And, you are right that we're not assigning the return value of $.getJSON. Basically, once $.getJSON has finished with the request, it will call a function with the result. This result is set to the variable passed to defer. It's a bit of a brain teaser but that's the whole point of the await/defer thingy. Instead of using a callback to retrieve the result as an argument, you just write a variable after defer and you receive it.
Does it mean that it's impossible to return any value from a function that contains await block?
module =
dataPath: "http://search.twitter.com/search.json?q=blah&callback=?"
init: () ->
# Fetch data and do some stuff with it
await
$.getJSON @dataPath, defer @data
# Return yourself (this won't work)
return @ waity = (forFn) ->
await forFn defer r1, r2
console.log "values of defer variables", r1, r2
return "this only returns from the result callback, not waity"
syncReturn = waity (cb) ->
setTimeout ->
cb "r1 val", "r2 val"
console.log "waity's synchronous return value:", syncReturn
So our output is: waity's synchronous return value: undefined
values of defer variables r1 val r2 val
To return, you can simply write: waity = (forFn) ->
setTimeout ->
await ...
return "works as normal"1. The floating title looks cool esp with greying out above it, but it page-down/space moves down more than a page (in effect), so it skips some text. I didn't realize I was missing some text for a while.
2. In the Animal example, the "Run" button doesn't work (though "run" works from the "load" overlay for it). I'm uising Firefox 9.0.1 on Ubuntu 10.04
Looking at first parallelSearch example, it could be like that (in regular CoffeeScript):
parallelSearch = (keywords, cb) ->
searches = [search k for k in keywords]
$.when.apply(searches, null).then cb
Although seems like it can't really cover all Iced's use cases. Hopefully the fork will continue to be maintained! for k, i in keywords
search k, defer out[i]
Do you really get lost or confused? Are you actually complaining about a problem, or just a violation of your favorite style guide? Personally, I'm looking for what makes IcedCoffeeScript unique; all else is just cruft, so keeping that terse is just fine with me.