-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Asynchronous coffeescript made easy, part II #287
Comments
Just a thought : would you guys prefer something like :
That is, to replace |
First of all, great work on the deferred branch. It would be an awesome feature. Here are my thoughts on it so far:
As for the
With regards to using
|
Great comments. I agree we can find something better than I personally think
|
LOL again, I was going to post this originally, but thought you had good reasons not to use |
:) Well, as posted initially, that's the keyword currently used in place of Hmm, now wondering what's the gain in putting a decorator like
|
I personally find |
Well, I introduced implicit callback as an alternative to "translate returns into callback invokation" which I think is not a good option. I agree that the implicit callback issue is far from being a necessity, since explicitness does not cost much in coffeescript (think That said, the thing with the decorator alternative is that it gets in the way when writing small wrappers. For instance, the following would be too verbose if we wrote it with decorators, instead of special arrows :
(A lot is going on behind this : the equivalent compiled javascript is 51 LOC! ) |
I've had a brief look at your code, and it's much nicer than my hacky progress. I don't yet know what it covers or is missing, but it looks pretty great. Thanks for working on it, I haven't had enough time to make any progress lately. The features, however, I still disagree with. I the use of a keyword to stand in for an argument is mostly an aesthetic one, though I'm still confident that having the defer keyword out the front of a call reads better than placing something inside the argument list. But that's presumably easy enough to change if we decide to. Similarly, I don't think we need another operator to specify that the function requires a callback (~> or ->>). It's just another thing to forget when coding, I think we should simply infer it from the use of The most important issue I think is having the yield (or whatever it gets called) construct as distinct from a return. I understand that you don't want to restrict the use of this feature to the single case of returning asynchronous values, but the way I see it:
Sorry to be so opinionated, but I have been trying to write an actual application with a heavily asynchronous datastore for a few months. I started with native javascript, then tried narrative JS, then strands, then async.js, and then I even wrote a macro-laden version of parenscript in order to solve the problem of asynchronous returns. So I feel like I have a pretty good grasp of what, exactly, needs to be solved. It isn't yet-another iteration mechanism, it is simply an issue of returns. in regards to those other libraries, narrativeJS (and strands) were too ambitious, and the original author admits they were going about solving the problem in the wrong way. My approach is almost identical (in functionality) to that provided by async.js, it's just a while lot more simpler to write and use. So it's not just me who has these specific ideas about what is required. |
What would the difference be between me going:
and
If there isn't any difference, why are you using |
Tim-Smart: Is there a difference between returning a value from a function and requiring a pointer to be in the arguments, eventually used as the place to store some value? Or is there a difference between The point is around the fact that many of the functions we use and produce in asynchronous/event-based contexts, and many others, requires a callback to be passed as the last argument. In those cases, which represents a big portion of them, instead of putting a name at the end of the argument list and remembering it ever time you need to invoke it, we suggested to make that transparent to the programmer, so it becomes straightforward. Most programmers are used to bigger conceptual challenges that those implied in learning that gfxmonk initially proposed to just used the gfxmonk: I can assure you a swapping of the current state of my branch to what you wish it was would be a matter of minutes. I can't say the same about our divergence of opinions :) I just hope we can find an agreement to get things moving forward... I truly believe an implicit callback mechanism would make our code lighter, though I agree, not more potent. It does also provide a really convenient and flexible plug for the Also, about the |
Briefly, to be clearer about an implicit callbacks (read Also, about the |
(Disclaimer: I'm sorry about bringing this forward all the times, but I'd really like it if we can get a consensus sometime, so we can get something into trunk. I'm currently developing a open-source server based entirely on Node.js and all its async features, so it would be nice if we could be able to start actual use of a async-friendly syntax). After some chat with jashkenas on irc, we concluded the feature is one that's really hard to wrap out our head around, so this stuff really needs to not be obscure, if we want it to be in core language. The feature should appear useful to anyone, for other purpose like in ajax jQuery for instance. The
Now for the way to yield values to the implicit callback. I still don't think we should mess with the
I don't see how we could explain that this will fire an implicit callback more than once, without confusing people. We could just settle on that good old "invoke the callback" syntax for now,
So now, we're back with simplicity, no more keywords than |
I like it. After seeing all the ideas - I've really come to the conclusion that it should be as simple as possible, and as little deviation from standard Coffee. In fact I'd even go further and say that only one of your two examples: [error, files]: defer fs.readdir "lib/" [error, out]: readSomeFile files[0], defer, lastarg should be allowed. I prefer the second as it's most inline with normal function calling, but either way we need a standard otherwise it's confusing (for no tangible gain). It seems we're close ! |
+1 for only adding |
I'm glad to hear the code itself isn't too divergent, even if our opinions are ;)
I believe that by making yield separate from return, you would be breaking half of the equation. Iterators and continuations should not be confused, and I think yield (or even resume) does exactly that. I maintain that "return" is exactly what you are doing (and the only thing you ought to be doing) when you call your continuation, so we might as well re-use that for the sake of familiarity, simplicity and conciseness. If you're not returning, it's not a continuation. As an aside, if you think we're dealing with arbitrary callback arguments, rather than only with continuations then that would be a departure from the original issue / feature suggested. There's nothing wrong with that, but the original feature is still the one I am wanting to address, because I don't think coffeescript needs an additional keyword as sugar for calling an implicit callback.
|
Oh, and I forgot to mention:
These conventions are not simply based on our sense of good practice. These conventions are existing conventions in every single piece of asynchronous javascript (and many other languages) code I have ever seen, aside from |
wow, not sure what happened there. When I posted those two comments, I hadn't seen past http://github.com/jashkenas/coffee-script/issues#issue/287/comment/170651 . weird caching? Thanks matehat for splitting out the yield syntax into another issue - they're not unrelated, but it'd be good to discuss the merits of this and that issue separately. Anyways, I think it's a good idea to have defer as either a prefix or an argument placeholder as you suggest, that way at least we only add one more keyword. Although as pointed out, it could cause more confusion. Sorry I wasn't on IRC when this was discussed, I've popped in a couple times but nothing much has been happening when I do - maybe because of timezones or something. One thing I noticed is that the following code is confusing because it "returns" nore than once:
in my proposal (replacing return), the actual generated code would include both a call to the callback, followed by a return. That is:
that is, every return statement is still the end of your function - code past a "return" will still never execute. I comepletely agree that without this, using "return" to mean "call my continutaion" would be a confusing misnomer. |
If you still can't come at the return idea (I may have failed to explain it fully before now, so please read the above and let me know if you still object), I'm okay with it not being in the mainline (yet). It's a small enough change that I'm happy to champion it as distinct from the "defer" continuation issue, as you have started to do with the "yield" syntax. Hell, maybe it'll become obvious that it does or doesn't make sense once people start actually using these features. The continuation-generation is really the hardest (and most useful) part of this whole business, so it's better to get that settled, and then we can see which (if any) of our suggested improvements are still worth adding on top of that functionality. To sum up (because I have a habit of long and winding replies), I'd love if we could get the defer functionality in master (is this an option, jashkenas?). It sounds like both my return-replacing functionality as well at matehat's yield operator should not go in at this point, but hopefully without either of these we can still start using the Thanks for being patient matehat, I know I've been pretty opinionated on this matter but it's something I really want us to get right, and it's kinda hard to convey ideas with text alone... |
gfxmonk: I'm happy with your approval of it moving without either of our divergent ideas for the moment. Sorry for being so opinionated as well :) Yeah, after some thought, I've come to decide to split the So, once we get approval of the current state, the next step would be to discuss overriding the |
Getting either of your branches merged into master is definitely possible. I just need to see what's going on (as soon as it's on master people will start to use it as their standard CoffeeScript install...) So the plan should be this: make sure you're passing all of the tests, and then post a comment or a ticket that links to the diff (or Github compare view) of your branch, explain the extent of the changes, and show a couple examples of CoffeeScript that uses If it's ready, we can take it from there. |
UPDATES It's moving quite nicely and the codebase is quite stable. We should discuss some of
|
great news :) 1). I would hope this can work, in a generic way, along the lines of:
would become, basically:
It's awkward, but that's the beauty of not having to write it manually ;) Is that possible? I haven't peered too closely at the current implementation of while loops, but I would expect it would have to be somewhat similar (with an iterate() method per-iteration, wrapped in a global "loop" function that could be broken out of at any time by simply calling the continuation)
|
hmm, I just realised that you can't assume an integer index into an arbitrary object. Possibly one would need to construct an actual list of iterable property names up-front, and then iterate through that list (instead of the object directly). |
I haven't looked in detail at the, but from a outsiders point of view, : does anyone ever really want to do: defer func(i) for i in list; ? I'd argue that edge cases should not be used if they complicate the code base (perhaps people do use it? ). Might be best to force the programmer to do it a consistent way since it must be doing some quite big code munging ? |
It's more common than you might thnk. In my RSS app with an async datastore, I often find myself using exactly this pattern. e.g:
where the function signature of items.get is It can be serviced with a mapAsync function that takes care of the iteration, but it would be cool to have coffeescript deal with it. I don't think it's an edge case, simply an awkward one ;) |
gfxmonk: makes sense. BTW, we can distinguish between the integer-array/key-object cases with the use of So, for aggregations, we can guarantee integrity and ordering and it shouldn't be so hard to implement, which I think I'd do through the new utility functions mechanism. Another ambiguous question is whether non-aggregating loops should have that sequential order machinery. Should we always wait for the preceding iteration to return before firing the next one, knowing no aggregation is performed, or just fire them all through a usual |
I think you are starting to face some of the concurrency questions the guys designing the Rx library for .NET had to solve not long ago. |
matehat: good point. For loops that don't (obviously) depend on ordering, spawning them off immediately and collating the results as they arrive is probably a good idea. The only real trick there is the bookkeeping to know when you are "done", and also the possibility of iterations depending on previous ones - you obviously can't protect against it 100%, but are there any normal cases where firing off |
The Do lib is interesting : http://github.com/creationix/do |
The 2 mentioned libraries provide a way for developers to decide whether they want chained async iterations or parallel async iterations. Both ways can be made so as to keep aggregates topologically correct. The problem is that we can't provide that choice for people to choose themselves. We should make a decision whether we want to decide for them, or we don't do smart (read: restrictive) stuff and let people handle it. |
perhaps it might be best to leave it out for now then (i.e, fail on using a defer in a for loop). An async-aware library is probably a good idea, and keeps us from having to code too many decisions into the language itself. Are any cases other than the for loop have this problem? As you said, while loops have an explicit ordering requirement. I think if/else branches should be evaluated sequentially, because the short-circuiting behaviour is pretty ingrained in all programming these days - it would be surprising to break that... |
The if/else and switch/case should not be problematic and, as you said, we can just make it the way people will expect it to behave. There doesn't seem to be any other issue for now, at least that I can think of. For the |
Yeah a parallel |
ok, I should push an up-to-date commit soon, when I get time (should be no later than the end of this week) |
If you don't have enough to read already ... here's an archive of papers on continuation implementation, for the record: |
Syntax idea, instead of [err, data] = defer get "/", {} async(x, y) [err, data] = defer more a, b setTimeout(defer, 100) final() use a loong arrow err, data = get "/", {}, --> async(x, y) err, data = more a, b, --> setTimeout -->, 100 final() I think it neatly encapsulates the idea of 'look to the next line' and also that it's a bit like a function. I've also snuck in an implicit array for multiple assignment ;-) |
More fun continuation-related reading for the adventurous: |
hmmm i was just thinking - is there really any benefit to this async stuff ? I mean really the main problem with the JS version is it's ugly and there's lots type type. In CS - it's really just an indentation - which really nicely encapsulates the idea of a async process. Since the methods suggested so far will run up to the end of the function, it means it's not especially useful since you cannot do something after the end of the async declaration? get "/", {}, (err, data) --> async(x, y) do_more() |
Hi, I'm sorry I haven't been able to make the project progress anyhow lately. I've been quite busy in my company's own projects and since things are going quite well and since I need to rush on a few things there, I won't be able to work much here... So, if anyone wishes to take the lead on that particular branch, my own deferred branch is up-to-date with latest trunk and all tests (except 1 or 2) pass. The special behavior of Closing the ticket. Anyone wishing to take the lead can create a part III :D |
Thanks for pushing your work, matehat. I'm away for the weekend, but I'm definitely happy to continue this work. I'll make a part III when I've got something ready for merging, as I think discussion has mostly slowed for now (anyone who disagrees should feel free to continue here or make their own III). |
oh sorry weepy, missed your post. yes, it will run to the end of the function. That's by design - if you want to do something after the async call, you should just use a callback. "really just an indentation" is still a big deal when you take this to a logical conclusion of say 8 async calls in a row. And the biggest boon is when you have an if block, or a loop interacting with callbacks. This is not at all trivial to hand code (I've tried), and is something where compiler help would be greatly appreciated. Once I've got this feature working, I can definitely provide some examples which hopefully prove its usefulness :) |
Hi folks,
I think we're slowing getting there. Here's an update:
I gave a shot at replacing the
~
character in positional arguments withdefer
(following weepy interesting suggestion). It makes it more obvious, as gfxmonk pointed out, that it's not just continuing execution and it appears in the called function arguments since, this way, we can have only one special keyword/character for the whole thing. I also kept the~>
arrow, because that gives us more flexibility than just finding the closest enclosing function foryield
, especially useful for a programming language where anonymous functions are used that much.Good news: a huge test suite based on gfxmonk's branch (and beyond) is passing on my branch. There are still one or two issues, though. You can take a look if you wish to see example usage.
http://github.com/matehat/coffee-script/blob/deferred/test/test_deferred.coffee
I'd also like to hear your thoughts on the whole
yield
/~>
issue. Keep in mind, though, that the choice of character,~>
, is really just a suggestion. The core question is about making the invokation of implicit callbacks transparent, while enforcing, with a different keyword, the fact that it's calling and not returning, all the while making the callback container explicit, somehow. I suggested it to be specified with a special character. It could really be anything, don't take a position just on how it looks and what glasses you need to wear when hacking on it.And nothing in there is magic, you can take my words for it ;)
P.S. gfxmonk I'd like to hear what you think about my implementation to date. Do you find it satisfying, or you think it's missing a point somewhere?
The text was updated successfully, but these errors were encountered: