Why do you as an American feel the need to invoke some dead german left-wing militants in a pseudo-religious phrase that's meaningless except maybe for shock value? This seems highly inapproriate for any website and even more so on HN.
Because "America" is part of the nation's actual name, and the only part that isn't a modifier. What else could you call Americans? Unionized Statists?
Continuations don't help with the problem that the visual structure of callback-oriented programs doesn't reflect the order of execution. As a heavy JS programmer, that's the most compelling point for me in this post.
> the visual structure of callback-oriented programs doesn't reflect the order of execution.
One of my bosses made this assertion about Object Oriented code that followed the Law of Demeter and other OO best practices. I don't think he's entirely the best OO person, or entirely on the right track. However, I would venture to say that all programming paradigms hit a point where visualizing the flow of control gets exhausting. If it's not a twisty maze of little methods, all looking the same, then it's a twisty maze of callbacks...
Callback-oriented code is different. With by-the-book OOP code you're still executing one line at a time. You might be teleporting in space, which has its own problems, but your code still reflects the order of execution.
With callback-oriented code you're teleporting in space and time.
They can both make it hard to trace the path of execution. At least with OOP code you have a sensible stack trace, though. ;)
So are Java/C#/everything-else exceptions. And, for that matter, pretty much every control flow construct imaginable.
OTOH, I think that the big problem with continuations is that it gets very difficult to build efficient implementations of them (and this tends to impact not just efficiency of code that uses continuation, but usually efficiency of any code in a language which supports them), and it is much more efficient to implement specialized weaker (but good enough for most key use cases) forms of the most important applications of continuations.
Supporting call/cc and dynamic-wind has a significant performance impact in some languages, even for code that does not use the features.
Supporting coroutine.create+coroutine.clone, shift+reset, or setcontext+getcontext+makecontext+swapcontext seems to have no performance impact on code that does not use the features.
In fact, Eric Lippert, when first introducing that feature on his blog started with a five-part series about continuations and only in the end got around to explaining what that was all about. It was a very nice read.
I think he was referring to features like call/cc. They are very similar to C#'s await in that you write regular looking code and the language does the heavy lifting of figuring out what the real continuation is supposed to be.
Delimited continuations are a huge improvement over undelimited ones, but still, by themselves, any kind of continuations feel like (to me) the GOTOs of functional programming.
More recently, people are doing work with "effect handlers". See Eff & it's research papers, for example:
I agree, although I think callbacks are more like COME FROM than goto. You see a function being passed somewhere as a callback, and you know the block is going to execute at some point, but most of the time you have no idea what the codepath that calls you back looks like.
There's nothing more frustrating than trying to debug why a callback isn't being called. Who calls it? How do I set a breakpoint somewhere to see why it isn't being called? etc.
The one thing that is still missing from await and other green thread approaches is cheap global contexts. Isolating every different green thread so they can't implicitly share state is the obvious next step.
Have you looked at the Erlang model? Each Erlang process (green thread) only gets the arguments initially passed in and whatever else it asks for from other running processes. The only shared state is long-running processes created for the purpose of explicitly sharing state.
Yep. The Actor model that Erlang uses is exactly what I was referring to being missing from green thread libraries for other languages (C#, python with greenlet or generators, js with generators)
"I have just delegated the bookkeeping to the compiler."
That's not obviously a good thing. Debugging the compiler (or just figuring out why it did something, even if correct) is far more difficult than debugging application code. Given the choice between implementing behavior with application code (or a library function) or adding semantics to the language, I prefer the former because it's much easier to reason about code written in a simple language than to memorize the semantics of a complex language.
This is a nonsensical comment, and I voted it down. The same point can be made about any time languages got a level higher. This kind of rejection of powerful in favor of complex-but-familiar is precisely what Bret Vector warns against in the Future of Programming talk[1].
If anything, `await` makes debugging easier because you don't have to untangle callbacks and jump back and forth. You're not supposed to “debug the compiler” because, well, you know, there are test suites and everything.
Yes, this is something that takes getting used to. Just like `for` loops, functions, classes, futures, first class functions, actors and many other useful concepts and their implementations.
As for your edit, I still can't agree with you.
You're saying:
>I prefer the former because it's much easier to reason about code written in a simple language than to memorize the semantics of a complex language.
The point of `async` is making the semantics more obvious. Is it much easier to reason about Assembler than C? I say it's not. Would it be for somebody with years of experience in ASM and none in C? Yes it would.
I think it just comes down to that. Callbacks seem simpler to you not because they are simpler (try explaining them to someone just learning the language, and you'll see what I mean), but because you got used to them. Even so, error handling and explicit thread synchronization make maintaining callback-ridden code painful. I think setting `Busy` to `false` in `finally` block is a great example (in the blog post). You just can't do that with nested callbacks—they are not that expressive.
Async allows you to think in structure (`for`, `if`, `while`, etc) about time, that's why it's powerful.
I still don't agree though, I edited my post as well to explain why I think this is exactly the moment you need to tweak the language, and not the libraries. (And this is the point Miguel was trying to make when he differentiated `async` from “futures” libraries, even from the one `async` uses, because they are irrelevant to the discussion.)
"Callbacks seem simpler to you not because they are simpler (try explaining them to someone just learning the language, and you'll see what I mean), but because you got used to them."
No, they're simpler in the literal sense: they introduce no new concepts into the language or runtime semantics. (The dynamic behavior is still complex, of course.)
"Even so, error handling and explicit thread synchronization make maintaining callback-ridden code painful. I think setting `Busy` to `false` in `finally` block is a great example (in the blog post). You just can't do that with nested callbacks—they are not that expressive."
Right -- nested callbacks aren't the answer, either. In JavaScript (where most of my non-C experience comes from), a good solution is a control flow function:
busy = true;
series([
function (callback) {
// step 1, invoke callback();
},
function (callback) {
// step 2, invoke callback();
},
function (callback) {
// step 3, invoke callback();
}
],
function (err) {
// finally goes here
busy = false;
if (err)
// ...
});
This construct is clear and requires no extension to the language or runtime.
This is fundamentally a matter of opinion based on differing values. I just want to point out that there's a tradeoff to expanding the language and to dispel the myth that callbacks necessarily trade off readability when control flow gets complex.
While that can be true, I feel like the author inadvertently overstated the amount of work that the compiler is doing here. This is really more of a case of syntactic sugar and not heavy-duty code reordering.
What if you have two buttons, search and 'be lucky'?
I guess you will have to await for a controller object that wakes up that if either one of the buttons is triggered, and then test what button was pressed.
Now what if you have add a search option dialog that can be invoked at any moment? I guess when the 'apply' button of the option dialog got pressed then this is still going to be a callback that sets global variables according to search option.
So, like any mechanism, this way of structuring code has its limits. If an event can happen at any stage of the flow, then this still has to be handled by a callback, otherwise you would have to check for this kind of event after each await clause.
In event driven programs there are often events that can arrive at any moment; for example in networking the connection might have been closed by the peer, or in a GUI the user might chose to alter parameters by means of option dialog.
So with await you may need to write a wrapper around await, the wrapper function will check for common events that can arrive at any moment.
Surely async isn't meant to replace events—it's just in some cases, when you expect events to happen in particular order, such as in help tutorial, async gives an advantage.
"What if you have two buttons, search and 'be lucky'? I guess you will have to await for a controller object that wakes up that if either one of the buttons is triggered, and then test what button was pressed."
Callbacks are basically COME FROM, epecially on a platform like a cell phone where you at least in theory have limited processing resources ($40 android phones need apps, too!). They are the devil.
No, they aren't even similar to COME FROM. COME FROM is "upon reaching label X, jump to this point". Callbacks have less in common with COME FROM than with GOTO, and less in common with GOTO than with normal procedure/function invocation.
> epecially on a platform like a cell phone where you at least in theory have limited processing resources
Platform is completely orthogonal to the relation between callbacks and other programming constructs.
Iced Coffeescript is brilliant! I think it is the closest you can get right now to sane development with callback-oriented code in Javascript.
Unfortunately it doens't solve the exception problem yet. Exceptions passed to callbacks (as `(err, value)`) are not thrown. So instead of try/catch there will be lots of `return cb(err) if err` in your code.
Yes a thousand million times. This is the reason why people love golang and why there's a lot of excitement about core.async in the Clojure community, particularly for ClojureScript where we can target the last 11 years of client web browser sans callback hell:
Having spent some time with ClojureScript core.async I believe the CSP model actually has a leg up on task based C# F# style async/await. We can do both Rx style event stream processing and async task coordination under the same conceptual framework.
This sounds sweet. In my book, C# designers have a history of striking a good balance between simplicity and flexibility. This however always leaves me eager to look at Haskell/ClojureScript/other languages where concepts that C# borrowed and simplified are taken to the full (such as monads, iterators, CSP, etc).
The C# designers outdid themselves this time. I'm surprised they managed to made this feature so simple and succinct, especially for an "enterprise" language. Those two little words (async/await) seem like something I would expect from a language like Python or Ruby. Had it been Java, it would be called AsynchronousBureaucraticProccessDispatcherFactoryFactoryFactory.
F# implemented this many years before (6 years ago), and as a library, just by providing the proper language feature, workflows, and a default async implementation.
In comparison, C# adds special compiler keywords for one specific example, just like they did with LINQ. That seems rather ugly IMO. Providing building blocks and letting libraries fill things in is a lot nicer.
This is more of a "C#'s finally catching up with basic features".
In F# workflows are just a syntactic sugar for monads, much like Haskell 'do' notation. You can get continuation monad (workflow) in F# easily. I don't know what ClojureScript uses, but it doesn't seem very likely that it has more powerful mechanism :)
Clojure/ClojureScript now has core.async, which is a Go-style implementation of CSP with coroutines and channels.
Like C#, core.async uses a lexical compiler transform to produce a finite state machine for the co-routine. Unlike C#, Clojure can achieve this with a user-level macro, instead of a compiler change. Both C# and core.async differ from Go, in that Go's coroutines have dynamic extend, by virtue of being heap-allocated stacks with a custom scheduler. In practice, this has a minor impact on higher-order usage of co-routines, but is a smaller problem than you'd think, it's generally advisable to minimize higher order usage of side effects.
Both C# and Go's approaches can be implemented as Monads, yes. However, Monads are a significantly more abstract thing than either CSP or C#-style Tasks. The do-notation is barely concealed continuation-passing style, which is generally less pleasant to work with than traditional imperative constructs for side effects such as send & receive. "More powerful" isn't a really useful measurement for practical use.
As Brandon alludes below monadic designs generally have allocation overheads, this is why C# uses state machines. So while they may be equivalent in some abstract sense of "power" one ends up being more efficient in practice.
Yes, C# tends to copy F# features in the way of compiler-syntactic-sugar. I wonder if Type Providers will be next. Async/await have been around for a couple of years and I haven't heard of the next big C# feature, other than Roslyn.
They seem to be pretty busy with Roslyn, Anders recently admitted it's taking longer than originally expected. So perhaps we need to give 'em a break. The only thing I heard about C# 6 so far is it's maybe going to have more compact class declarations, a-la F# or TypeScript.
C# 2 added generics (courtesy of the same people that did F#) and closures (albeit with syntax as verbose as JS).
C# 3 added LINQ, which is a major breakthrough for end-users, although I'm not fond of the query language. So really, C# 3 just added in some basic features you expect from proper languages. I do understand this required a huge amount of work, esp. with the tooling required.
C# 4 added dynamic (F# provides ? operator you can provide your own implementation for, if you really feel that strings look ugly). Oh, and it finally backpedalled on the no optional parameters (although the optional parameters is the same broken C-style callsite implementation).
C# 5 added async (F# had a more flexible implementation 6 years before).
What else? C# seems to have stagnated, although I understand that's a feature for some of their users. C# still lacks type inference in most places, making it extra verbose. C# expression trees are still very limited. C# still can't easily do tuples. Not sure they deserve a break; this was MS's "flagship" language.
OTOH, The CLR itself doesn't seem to be getting any upgrades, either - IL stayed locked at v2. It's as if they realised they have done better than the JVM can ever do (generics via erasure just sucks) so why bother pushing it further?
I'm not sure what you mean by break, but the comparable time line for Java, and C++ would show far less improvement in terms the addition of programing languages (as in the academic discipline) features (e.g lambdas, closures). They've done a far better job than most languages of actually advancing the language conceptually, not adding libraries/features.
TO me that seems to be a good thing. The language has picked up a lot of great features that make it nice to program in, but it is also making the language huge. How do all these new features interact? What are the emergent properties of the language? Personally I could use a few years to A) get legacy code caught up B) Explore what already exists.
I don't think it's fair to characterize this as "catching up". Both F# and C# are developed by an overlapping group of people at Microsoft. And, until recently, the bulk of Haskell's GHC was done by SPJ in a closely collaborating group in Microsoft Research.
The correct characterization is to view this as a pipeline from a research language, to a specialists' language, to a common man's language.
Six years isn't really all that long to wait for a specialist feature to be 1) motivated 2) conceived 3) prototyped 4) validated 5) justified 6) implemented 7) tooled 8) released 9) marketed. Given that there are hundreds of ideas and only so much time, the "minus 100 points rule" [1] basically means that it's no easy feat for a feature like this to show up in a mainstream language. When you consider the quality bar, level of IDE integration, the magnitude of the education effort, and all the other odds and ends, it's something of a minor miracle.
C# operates entirely differently, I understand. The IDE work is massive and necessary. Having said that, a lot of this stuff is "catching up" or implementing stuff from the 70s. To be clear, it's not like type inference or closures were invented with Haskell, F#, or C#. Stuff like that is pretty well-known PL stuff, isn't it?
People would be upset if C# didn't have for loops; why aren't they upset the type inference is nearly useless?
Probably because they don't know it's useless. I use and like C#. The type inference seems useful to me. Avoiding generic parameters on almost every linq extension method is a huge savings in comprehensibility. var x = new SuperDuperLongClassName(); is a nice savings in redundancy.
Where can I see an example of useful type inference?
People would be upset if C# didn't have for loops; why aren't they upset the type inference is nearly useless?
Can you honestly not comprehend the answer to this? There are plenty of languages without type inference and shit gets done fine. People don't rely on it. People do rely on for loops.
What people like to accuse Java for, I have seen enterprise architects do such examples in C, Perl, C++, Java, C# and about any other language used in enterprise context.
Yeah, C# await/async is cool. But isn't Google Go's approach nicer? Keep standard lib calls blocking as is, and use the "go" statement when you want to run something async? It avoids all the extra DoWhateverAsync() API functions. See http://stackoverflow.com/a/7480033/68707
What's the Go equivalent of "await"? I.e., like the "go" statement but asynchronously wait for the function to return and get its value?
The difference is that Go has what looks like preemptive scheduling for goroutines (and will eventually be truly preemptive; see [1]) while await is more like cooperative multitasking. If you're writing in Go, you should use channels (or mutexes) to avoid race conditions: "share by communicating".
With a single-threaded language using await, it's safer to modify common data structures without locks, although you should be aware that calling a function via "await" gives others task the opportunity to modify your data. So "await" appearing in the code explicitly warns you that pseudo-multitasking is happening. In Go, preemption can (in theory) happen at any time, plus there are multiple threads.
The ease with which callbacks can be created leads people to create them carelessly and excessively. While I like what the article has to say, there are ways to write callback heavy code that do not get ugly so fast. Looking at the iOS nested block example from Marco Arment -- the first step is to not do everything inline. Then the code suddenly becomes clear and the argument becomes one of syntax sugar.
Comparing callbacks to goto is a tad unfair. They don't merely solve async issues, but also event handling and dynamic systems to name two common uses. I don't see a better solution on the table. Using callbacks to write deeply async code is the real problem. And while async/await may help with this problem, it still won't tell you why step 3 never finishes, because it's still waiting for a come from.
First: code that performs simple sequential steps should look simple. With callbacks, it always ends up looking complicated.
Second: refactorability in callback oriented code is a lot worse than linear code. Even refactoring code with a single callback can be annoying. Async code with callbacks that looks and feels like imperative synchronous code is an enormous gain.
First: you're hammering the async point, which I don't disagree with. I am just pointing out that you can write less horrible async code than the example cited with callbacks.
Second: wait until people write ludicrous numbers of async routines as if they were imperative because it's so easy. Calling one from another (and hooking them up to event handlers). You'll have the same damn problem one level removed.
Code that performs simple sequential steps should be synchronous.
Keep the asynchronous complications at the code that perform non-sequential steps. And yes, I'm fully aware that some libraries (Javascript's one, the guilty are always the same few) force you to use asynchronous calls. That's a flaw of the library.
I am very happy that my current (PhD) code doesn't require me to deal with blocking calls to things (it's just one massive calculation essentially). I remember this nastiness from when I had a real job, and I'll no doubt have to deal with it again when I escape academia.
This article is very interesting - I enjoy articles which spell out the usefulness of a new language feature. I haven't used C# for a few years, and this is a great advert for coming back to it one day.
std::async uses threads (if used with std::launch::async) whereas C# await uses coroutines. You can use Boost.Coroutine to implement something similar in C++ as that project has done: https://github.com/vmilea/CppAwait
This definitely is a problem in Obj-C. Using GCD and callbacks is usually easier to understand than delegates and manual thread management, but it's still not great. I would love to see something like async/await in Obj-C. There are some great ideas on how to get something similar in this blogpost, but none that I would use in production code unfortunately:
http://overooped.com/post/41803252527/methods-of-concurrency
"Await" is fantastic, and having using it for JavaScript (via TameJS and then IcedCoffeeScript), it makes things a lot easier and clearer.
That being said, I don't think the comparison between callbacks and goto is valid.
"Goto" allows you to create horrible spaghetti-code programs, and getting rid of it forces you to structure your programs better.
"Await", fundamentally, isn't really anything more than syntactic sugar (except for exception handling, which is a good thing). "Await" doesn't change how your program is structured at all, it just changes the visual representation of your code -- from indentations in a non-linear order, to vertical and linear order. It's definitely a nice improvement, and makes code easier to understand (and allows for better exception handling), but it's not actually changing the way your program is fundamentally structured.
And finally, "await" is only applicable when a single callback gets called once at the end. If you're passing a callback that gets used repeatedly (a sorting function, for example), then normal-style callbacks are still necessary, and not harmful at all. Sometimes they can be short lambdas, sometimes they're necessarily much larger.
In sum: "await" is great, but there's nothing inherently harmful about callbacks, the way "goto" is. To the contrary -- callbacks are amazingly useful, and amazingly powerful in languages like JavaScript. "Await" just makes them nicer.
Await does change how your program is structured, unless we're talking about most trivial cases.
You can use `await` inside a `for` loop—can you do the same with callbacks without significantly re-structuring your code?
What is the “callback analog” of placing something in a `finally` block that executes no matter which callback in a nested chain fails? You'd have to repeat that code.
Await has a potential of simplifying the structure a lot, because it befriends asynchronous operations with control flow.
>And finally, "await" is only applicable when a single callback gets called once at the end. If you're passing a callback that gets used repeatedly (a sorting function, for example), then normal-style callbacks are still necessary, and not harmful at all.
Indeed, await is only for the cases when we abuse functions (because we don't know what we'll call, and we have to bend our minds). Passing the sorting comparator is the primary use of first-class functions.
I guess we're using "structured" in different ways. Libraries like TameJS do wind up creating structures to deal with for loops, in the same way you'd otherwise manually have to deal with. Likewise with exceptions (which I said are the main actual benefit to await, that can't be reproduced in normal callback routines).
You obviously have to write a bunch of "plumbing" code to do with "raw" callbacks, in complicated situations (like loops), which "await" does on its own -- and writing that plumbing is annoying, although there are libraries to help.
My only point is, the fundamental structure of your program, on a conceptual level, is still the same. Everything's still running the same way, in the same order. It's just more concise, with less plumbing of your own, using "await". So the micro structure is different with await, but the high-level structure is no different. You can't "abuse" callbacks in the way you can abuse goto. Maybe I should have made that clearer.
I see your point. Closures also don't affect structure on conceptual level, but I think they're pretty darn useful. By the way, async can affect things on conceptual level if you embrace[1] it (I posted this link somewhere below as well, but just in case you haven't seen it).
There are other means of accomplishing what you are referring to, for example, the async module for node.js is really nice in terms of having loop workers, as well as flattening out callback structures.
I find that async.waterfall + SomeFunction.bind(...) are insanely useful with node.js ... I don't seem to have near the friction in node that I find when working in C# projects.
The genius about them, however, is that with all those, we discovered that you could get rid of "goto" afterwards. Which was kind of amazing.
"Await", on the other hand, doesn't remove the need for callbacks -- it just makes it much easier to use them in a lot of common use cases, in a much clearer way. But there are still plenty of valid/necessary uses for callbacks that can't be handled by "await".
Await kills `done` and `error` callbacks, which are always devoid of concrete meaning in the context of function. Of course it can't—and isn't meant to replace callbacks like `comparator`, `predicate` etc.
On the contrary, there are still perfectly good uses for goto. The two I can name right off the top of my head are stack-like error unwinding in C (which comes with an endorsement from CERT recommending its use) and computed goto dispatch tables in threaded interpreters.
Still, you're right that structured control flow statements have obsoleted goto for all but the tiniest edge cases, and so too do I look forward to callbacks suffering a similar fate at the hands of things like await.
What this suggests is that "await" isn't like all structured programming, it's like one control construct. The history of structured programming is the development of more constructs as people realised that they had a goto pattern that kept popping up in their code, so they named it and thereby got a cognitively-higher-level program. Long after Dijkstra's essay, we could still occasionally find new places where goto was really the best way to do it: for instance, if you wanted to "break" out of multiple loops at once. So someone invented named break (likewise continue), and removed yet another use of the unconstrained goto in favour of a more constrained, structured construct.
Taking the OP's premise at face value, then, if callbacks are like goto, then await takes away one place where they were needed and replaces them with something more structured and safer---and this doesn't at all negate the possibility that there are other constructs yet to be invented that would continue that process.
Another example: a lot of old code bases use "goto" for error handling. I'm assuming the Linux kernel still does this. Now we've formalized that behavior into exceptions.
You need C++'s RAII idiom or go's defer syntax to get the same behaviour though. I think try-with-resource in Java also would do the same thing, maybe. Just plainly throwing an exception won't release what you've acquired.
Something like go's defer makes most uses of goto (failure handling) unnecessary. However, there is still the "code a state machine" use case for goto.
Actually, I think that mutually-recursive functions are more semantically clear than goto for a state machine, though using explicit state objects is even more clear than either (though probably less efficient.)
ensure: blocks were how it was done in Smalltalk. You simply had a block of code that was followed by another block of code that the system would ensure the execution of. There was slightly more to it. You had to make sure that whatever ran in that block wouldn't take too long to complete, for example.
Formalized that behavior into exceptions? Last I checked using exceptions for control flow was a BAD practice.
In c at least, breaking out of an error condition is still best handled with a goto (where you can clean up all matter of memory in an organized fashion without littering your code with if elses).
This discussion sort of hinges on being able to explain why something is a bad practice or not. What problems in particular do exceptions cause, and in which situations do they not apply? In other words, is it ever right to throw an exception, or is every line of code in your program "control flow?"
It's been nearly a decade since I've used plain C, so I'm not 100% sure, but I was under the impression that it doesn't have any native support for exceptions. So yes, under those circumstances "goto" would definitely be appropriate.
In C#, you can implement resource ownership with the IDisposable interface and "using" keyword, which guarantees that once you leave the block (via an exception or regular control flow), the resource is cleaned up. In C++, you can use the RAII pattern that another commenter brought up. What other problems do exceptions introduce?
In my as-functional-as-is-practical programming philosophy, you throw an exception when there is no valid output for your function. Whether or not that's recoverable is up to the client to decide. Nulls are an extremely poor substitute for this, as they push the responsibility of output validation onto the client, and every single line of code must be enclosed in an "if (foo != null)" block.
I was initially very skeptical at first too. Then I noticed that the positioning of the "Busy = false" statements had been reduced to a structured form exactly as if we had started with an unstructured GOTO or multiple exit point control flow.
As a C++ guy, I don't like his "Busy = false" system to begin with. It would seem much better (to me) if he used a non-copyable object to represent the outstanding activity. Such an object could naturally reset the "Busy" flag in its destructor. But usually there's a better scheme than using a simple Boolean flag to represent a "busy" state anyway. (How is clearing the flag going to release the next guy waiting for the resource?)
So while I'm still a bit skeptical of drawing conclusions from this, I readily admit it's not so off-the-wall as I'd originally thought.
I'm sure Busy isn't meant to represent state—it's a property whose setter and getter call a spinning wheel UI element's StartAnimating and StopAnimating. It's a very common practice in iOS view controllers. That's what I read anyway.
Well if the goal is to have a "spinning wheel UI element" to reflect to the user that the app is in a "busy" state, and the UI element requires calls to modify its animation state, then no I don't have a way to do it without calls to the UI element.
for/while are themselves syntactic sugar around goto. You can in fact write them as macros or even functions (using setjmp/longjmp) in C. However, the abstraction in that case will leak - you'll need to put your label manually where the loop needs to go, you'll still need to know you're using goto and how it works and you'll need to keep in mind the exact code that's generated by the macro, or you will get bit by it. Structured looping constructs also don't change the flow in your program and even force another level of indentation, but they allow you to think about it using higher-level building blocks being sure that those blocks always work properly, i.e. they won't break because you have a typo in some conceptually far away line.
Callbacks really are just like goto. I've seen really awful callback code where you have callbacks that create other callbacks which are passed to callback managers which are themselves finite state automatons and call one of the callbacks based on a return value from another. It's the most horrifying spaghetti code you can think of. It's practically fractal - spaghetti within spaghetti that influence the top layer in an untraceable manner.
While everyone can write spaghetti in any language, few can write good code when given just goto or just lambda. In some cases it's even impossible.
Await is basically a continuation-passing style transform that puts the continuation in the continue handler of the task. The exception handling is also no more or less syntax sugar than the CPS rewrite - it's an error continuation that needs to be routed by querying the underlying Task's properties.
But the really great thing is that you can finally just thread the async keyword through your call stack to get the CPS effect across multiple method call boundaries, even your case of a sorting function. It's not just applicable for a single callback; the second half, the implementation half, is also implemented, so you're not just limited to using the pattern, but creating new instances easily too.
> I don't think the comparison between callbacks and goto is valid.
What is the largest code base you have worked on that used events + callbacks extensively? I worked on a 250,000 lines-of-code Java trading system, and understanding the way the code flow worked from the central event dispatch loop to handler functions, and back to the event dispatch loop took me MONTHS and seriously spoiled the quality of my life (considering how much many hours we spend at work).
Have you used alternative paradigms like Functional Reactive Programming, Promises, etc;? Maybe you don't realize how awesome the alternative is?
If await isn't changing the way you structure your code -- and letting your code robustly handle things that it couldn't handle with callbacks -- I think you're doing it wrong.
There's nothing inherently harmful about goto either, and people who think that just having it around is death, seriously don't understand the machines they're programming, and how we implement these magical control structures they love so much. (hint: we use goto)
I don't feel any respect for the article because it's written on the premise that goto is bad, and that it is anything like a callback.
Callbacks have been, and will continue to be an incredibly useful way to handle events.
I don't think the article is written on the premise that Goto is inherently bad, I think it's written on the premise that it can lead to unmaintainable code if misused.
I've had some of the exact situations described in the article come up many times at work — especially with blocks in Objective-C (nested error handling, recursive blocks, and so on). The `await` keyword seems like a very nice tool for a lot of common use cases that can be problematic with blocks/callbacks.
Scala has both Futures and Promises which are inherently much nicer than that async call. That's what happens when you allow things to compose. Glad c# gets something.
In the right places and for the right reasons they are fine. A lot of code today devolves into what I've come to call "callback spaghetti" and, well, good luck. The toughest thing sometimes is getting your mind around what is supposed to happen and, more importantly, what is not.
I found that sometimes it helps to build a state machine to effectively run the show and try to limit callbacks to setting flags and/or navigating the state tree. State machines make following code functionality a breeze, even when dealing with really complex logic.
Saying that, I don't mind the way things look with the whole await/async stuff in C# and etc. However I don't think we should be waving our arms around saying callbacks are like goto, they so completely are not! I have written heaps of stuff with callbacks and it's _not that confusing or unmaintainable_. It's just different.
foreach (var player in players) {
while (true) {
var name = await Ask("What's your name");
if (IsValidName(name)) {
player.name = name;
break;
}
}
}
Assuming `Ask` is an asynchronous operation and must not block the UI thread.
Note that second player is only asked after the first player has given a valid name.
(And the code structure reflects that :-)
My point is of course it's doable with callbacks, but I spent more time indenting this code than writing it, and I darn well know I'm not smart enough to spell out the correct callback-style code in a comment field on Hacker News. And if I suddenly had to add error handling...
for player in players
get_name_for = (player) ->
ask "what's your name?", (response) ->
if is_valid_name response
player.name = response
else get_name_for player
get_name_for player
This looks like it does the wrong thing. If "ask" gets to access the scheduler's task list as a queue, then entering an invalid name in the first response and only valid names thereafter will cause the first valid name to be given to the second player, the second valid name to the third player, and so on. If "ask" gets to access the scheduler's task list as a stack, then a sequence of only valid input names will cause the last player to have the first input name, the second-last player to have the second input name, and so on.
Edit: I was optimistically assuming that the consumer of the many "asks" that are created all at once would process them sequentially, dealing with one and invoking the callback before dealing with the next. If you do not assume this, my problem disappears and you get the simpler problem of spawning many prompts simultaneously.
Hmm, I wrote it so that the function closes over the player, so that shouldn't happen. The real issue is, as pointed out, that this will ask for all the names at once, rather than sequentially. Wether this is bad or not depends on how the `ask` function gets its input.
players.forEach(function() {
'use strict';
var player = this;
var name = Ask("What's your name?, function(name) {
if (isValidName(name) {
player.name = name;
}
});
});
If you're doing everything sequentially anyway, why bother with the awaiting part? As far as I can see your example would be functionally unchanged if you wrote the same code except without the await keyword.
Because it doesn't block the thread. The idea is that this code is executed inside of a thread that, if it blocks, will cause the application to hang. For example, in a GUI or a server. So, if its a thread driving a GUI, and it's blocked on user input, then the entire application interface will be unresponsive until it receives that input.
(function(players) {
var cur_idx = 0;
function cb(name, err) {
// just kidding, not going to handle errors!
if (IsValidName(name)) {
players[cur_idx].name = name;
cur_idx++;
}
if (cur_idx < players.length) {
Ask("What's your name", cb);
}
}
Ask("What's your name", cb);
})(players);
This is of course completely awful. just2n's reply is a nicer realization of the same concept. sprobertson's reply clearly will not work as written, and I don't expect it's even possible to condense this code into a single call to eachSeries.
I'll admit this took a lot longer to reason through than your example, but at the end of the day it's just a simple recursion (and I actually think the force validation bit is simpler in this version). Interesting exercise, and I think it ended up validating your point for me:
function makeAskPlayer(i) {
return function askPlayerHisName() {
console.log("askPlayerHisName: " + i);
Ask("What's your name?", function(name) {
if (isValidName(name)) { players[i].name = name; }
else { return (makeAskPlayer(i))(); }
// name was valid, get next one
if (i < players.length-1) (makeAskPlayer(i+1))();
});
};
}
(makeAskPlayer(0))();
Here's a simple test case showing it works: http://jsbin.com/ojakuy/2/edit. You need to open the console to see the output.
Here is an example where callbacks are even less intuitive:
if not song.artist
@getArtist song.id, (err, artist) =>
song.artist = artist
@save song
@addToCatalog song
#...
else
@save song
@addToCatalog song
#...
Callbacks will force you to move @save, @addCatalog, ... into a separate function. Completely messing up the logical sequence of operations.
More lines than the foreach loop, but on the other hand, if this was an operation you wanted to do in parallel instead of sequentially, that'd be impossible with the simple loop construct.
Point taken, yet this particular problem with players asked one after the other is simpler than the case when players are each spawned their own Ask. Here's a simple solution to your problem:
async.eachSeries(players, askName, function (err) {});
// this is someplace it should go
function askName(player, callback) {
Ask("What's your name", function (err, name) {
if (err) { return callback(err); }
if (IsValidName(name)) {
player.name = name;
callback(null);
} else {
askName(player, callback);
}
});
}
This follows Node's err convention. Note I use a bit of old fashion recursion to handle the asking for a valid name. This will not stack overflow due to the fact Ask is async. You could inline askName, but then you wouldn't have a nice little unit testable function.
function askPlayers(players, fn) {
if (!players.length) return fn();
var player = players[0];
ask("What's your name", function(name) {
if (isValidName(name)) {
player.name = name;
players.shift();
}
askPlayers(players, fn);
});
}
lthread is a coroutine library that allows you to make blocking calls inside coroutines by surrounding the blocking code with lthread_compute_begin() and lthread_compute_end(). This is equivalent to async calls but without the need to capture variables.
Promises were removed from node core a long time ago, and remained unpopular until very recently. They are making a comeback due to lobbying in standards committees, forward-compatibility with ES6 generators, and the jQuery effect.
I noticed that most of the methods awaited on had an Async suffix in their name. Is that some sort of modern hungarian notation, and is it even necessary? It also looks like you can't pass timeouts to await.
It is a convention, often used to differentiate blocking and asynchronous methods in the APIs (e.g. `Read` and `ReadAsync`, etc). You're not required to use it, but it is useful whenever there is a chance of confusion.
As for the timeouts, it would be strange to bake this into a language (different platforms may support different timers, at the very least).
Instead, you use library for this[1]:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
Instead of awaiting on a task, you await on `WhenAny` combinator.
The Async suffix is here to differentiate with the synchronous version of an existing method returning T, the asynchronous return a Task<T>. With a Task<> you can do :
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
According to the Microsoft doc[1] it is convention for async to return a Task but not a requirement. Since the return type of Task tells you a method is expected to be used asynchronously, also encoding that in the name seems somewhat redundant. I guess you have to use a different name because of static typing so Async is as good a suffix as any.
Instead of using callbacks, golang embraces synchronous-style calls and makes them asynchronous by switching between goroutines (lightweight threads). gevent (for Python) does something similar. It's certainly an interesting approach IMO.
286 comments
[ 3.3 ms ] story [ 268 ms ] threadhttps://gist.github.com/cscheid/6241817
As an Indian I am confused why only people of the US are called Amerians while two entire continents are called America.
And also, why we Indians are not considered Asians by the said Americans.
One of my bosses made this assertion about Object Oriented code that followed the Law of Demeter and other OO best practices. I don't think he's entirely the best OO person, or entirely on the right track. However, I would venture to say that all programming paradigms hit a point where visualizing the flow of control gets exhausting. If it's not a twisty maze of little methods, all looking the same, then it's a twisty maze of callbacks...
With callback-oriented code you're teleporting in space and time.
They can both make it hard to trace the path of execution. At least with OOP code you have a sensible stack trace, though. ;)
OTOH, I think that the big problem with continuations is that it gets very difficult to build efficient implementations of them (and this tends to impact not just efficiency of code that uses continuation, but usually efficiency of any code in a language which supports them), and it is much more efficient to implement specialized weaker (but good enough for most key use cases) forms of the most important applications of continuations.
Supporting coroutine.create+coroutine.clone, shift+reset, or setcontext+getcontext+makecontext+swapcontext seems to have no performance impact on code that does not use the features.
Even if you're not, others may enjoy this argument against call/cc by Oleg:
http://okmij.org/ftp/continuations/against-callcc.html
Delimited continuations are a huge improvement over undelimited ones, but still, by themselves, any kind of continuations feel like (to me) the GOTOs of functional programming.
More recently, people are doing work with "effect handlers". See Eff & it's research papers, for example:
http://math.andrej.com/eff/
This model is safer, faster, easier, and more composable than general purpose undelimited continuations.
There's nothing more frustrating than trying to debug why a callback isn't being called. Who calls it? How do I set a breakpoint somewhere to see why it isn't being called? etc.
The one thing that is still missing from await and other green thread approaches is cheap global contexts. Isolating every different green thread so they can't implicitly share state is the obvious next step.
That's not obviously a good thing. Debugging the compiler (or just figuring out why it did something, even if correct) is far more difficult than debugging application code. Given the choice between implementing behavior with application code (or a library function) or adding semantics to the language, I prefer the former because it's much easier to reason about code written in a simple language than to memorize the semantics of a complex language.
[edited to replace sarcasm]
If anything, `await` makes debugging easier because you don't have to untangle callbacks and jump back and forth. You're not supposed to “debug the compiler” because, well, you know, there are test suites and everything.
Yes, this is something that takes getting used to. Just like `for` loops, functions, classes, futures, first class functions, actors and many other useful concepts and their implementations.
As for your edit, I still can't agree with you.
You're saying:
>I prefer the former because it's much easier to reason about code written in a simple language than to memorize the semantics of a complex language.
The point of `async` is making the semantics more obvious. Is it much easier to reason about Assembler than C? I say it's not. Would it be for somebody with years of experience in ASM and none in C? Yes it would.
I think it just comes down to that. Callbacks seem simpler to you not because they are simpler (try explaining them to someone just learning the language, and you'll see what I mean), but because you got used to them. Even so, error handling and explicit thread synchronization make maintaining callback-ridden code painful. I think setting `Busy` to `false` in `finally` block is a great example (in the blog post). You just can't do that with nested callbacks—they are not that expressive.
Async allows you to think in structure (`for`, `if`, `while`, etc) about time, that's why it's powerful.
[1]: http://vimeo.com/71278954
I still don't agree though, I edited my post as well to explain why I think this is exactly the moment you need to tweak the language, and not the libraries. (And this is the point Miguel was trying to make when he differentiated `async` from “futures” libraries, even from the one `async` uses, because they are irrelevant to the discussion.)
No, they're simpler in the literal sense: they introduce no new concepts into the language or runtime semantics. (The dynamic behavior is still complex, of course.)
"Even so, error handling and explicit thread synchronization make maintaining callback-ridden code painful. I think setting `Busy` to `false` in `finally` block is a great example (in the blog post). You just can't do that with nested callbacks—they are not that expressive."
Right -- nested callbacks aren't the answer, either. In JavaScript (where most of my non-C experience comes from), a good solution is a control flow function:
This construct is clear and requires no extension to the language or runtime.This is fundamentally a matter of opinion based on differing values. I just want to point out that there's a tradeoff to expanding the language and to dispel the myth that callbacks necessarily trade off readability when control flow gets complex.
http://praeclarum.org/post/45277337108/await-in-the-land-of-...
Basically, the author implements a “first time walkthrough” kind of interface a-la iWork very declaratively by using async:
Now what if you have add a search option dialog that can be invoked at any moment? I guess when the 'apply' button of the option dialog got pressed then this is still going to be a callback that sets global variables according to search option.
So, like any mechanism, this way of structuring code has its limits. If an event can happen at any stage of the flow, then this still has to be handled by a callback, otherwise you would have to check for this kind of event after each await clause.
So with await you may need to write a wrapper around await, the wrapper function will check for common events that can arrive at any moment.
http://msdn.microsoft.com/en-us/library/hh194796.aspx
No, they aren't even similar to COME FROM. COME FROM is "upon reaching label X, jump to this point". Callbacks have less in common with COME FROM than with GOTO, and less in common with GOTO than with normal procedure/function invocation.
> epecially on a platform like a cell phone where you at least in theory have limited processing resources
Platform is completely orthogonal to the relation between callbacks and other programming constructs.
And platform is not orthogonal: sometimes I need to know exactly what is using how many cpu cycles, and callbacks make it hard.
Admittedly I mostly do embedded dev, but there's a reason why I have the only remote control / video app out there that still works on a HT G1 :)
Unfortunately it doens't solve the exception problem yet. Exceptions passed to callbacks (as `(err, value)`) are not thrown. So instead of try/catch there will be lots of `return cb(err) if err` in your code.
http://swannodette.github.io/2013/07/12/communicating-sequen...
Having spent some time with ClojureScript core.async I believe the CSP model actually has a leg up on task based C# F# style async/await. We can do both Rx style event stream processing and async task coordination under the same conceptual framework.
In comparison, C# adds special compiler keywords for one specific example, just like they did with LINQ. That seems rather ugly IMO. Providing building blocks and letting libraries fill things in is a lot nicer.
This is more of a "C#'s finally catching up with basic features".
Like C#, core.async uses a lexical compiler transform to produce a finite state machine for the co-routine. Unlike C#, Clojure can achieve this with a user-level macro, instead of a compiler change. Both C# and core.async differ from Go, in that Go's coroutines have dynamic extend, by virtue of being heap-allocated stacks with a custom scheduler. In practice, this has a minor impact on higher-order usage of co-routines, but is a smaller problem than you'd think, it's generally advisable to minimize higher order usage of side effects.
Both C# and Go's approaches can be implemented as Monads, yes. However, Monads are a significantly more abstract thing than either CSP or C#-style Tasks. The do-notation is barely concealed continuation-passing style, which is generally less pleasant to work with than traditional imperative constructs for side effects such as send & receive. "More powerful" isn't a really useful measurement for practical use.
C# 3 added LINQ, which is a major breakthrough for end-users, although I'm not fond of the query language. So really, C# 3 just added in some basic features you expect from proper languages. I do understand this required a huge amount of work, esp. with the tooling required.
C# 4 added dynamic (F# provides ? operator you can provide your own implementation for, if you really feel that strings look ugly). Oh, and it finally backpedalled on the no optional parameters (although the optional parameters is the same broken C-style callsite implementation).
C# 5 added async (F# had a more flexible implementation 6 years before).
What else? C# seems to have stagnated, although I understand that's a feature for some of their users. C# still lacks type inference in most places, making it extra verbose. C# expression trees are still very limited. C# still can't easily do tuples. Not sure they deserve a break; this was MS's "flagship" language.
OTOH, The CLR itself doesn't seem to be getting any upgrades, either - IL stayed locked at v2. It's as if they realised they have done better than the JVM can ever do (generics via erasure just sucks) so why bother pushing it further?
- Improve the GC algorithms, most JVMs have better GC algorithms
- Improve the code quality of the JIT and NGEN compilers, specially the set of applied optimizations
- Expose auto-vectorization and vector instructions (similar to Mono.SIMD)
- Expose something like C++ AMP on .NET.
Some of this was slightly improved on 4.5, but they could do more.
Not to mention that you are forgetting all the other JVMs that are also available out there.
Also it the set of tuning options is pretty thin compared with what most JVMs, Haskell and other GC environments offer for tuning.
Note that this is not a Java vs .NET rant, I work on both environments. Each has plus and minus.
TO me that seems to be a good thing. The language has picked up a lot of great features that make it nice to program in, but it is also making the language huge. How do all these new features interact? What are the emergent properties of the language? Personally I could use a few years to A) get legacy code caught up B) Explore what already exists.
The correct characterization is to view this as a pipeline from a research language, to a specialists' language, to a common man's language.
Six years isn't really all that long to wait for a specialist feature to be 1) motivated 2) conceived 3) prototyped 4) validated 5) justified 6) implemented 7) tooled 8) released 9) marketed. Given that there are hundreds of ideas and only so much time, the "minus 100 points rule" [1] basically means that it's no easy feat for a feature like this to show up in a mainstream language. When you consider the quality bar, level of IDE integration, the magnitude of the education effort, and all the other odds and ends, it's something of a minor miracle.
[1]: http://blogs.msdn.com/b/ericgu/archive/2004/01/12/57985.aspx
People would be upset if C# didn't have for loops; why aren't they upset the type inference is nearly useless?
Where can I see an example of useful type inference?
I did a pretty fair line-by-line translation of a C# app to F#, and the F# version needed 1/20th the number of type annotations.
Because the existing type inference is good enough for the average enterprise programmer.
You know, those guys with good enough CS grades, that just do what they are told and don't even know HN exists.
Can you honestly not comprehend the answer to this? There are plenty of languages without type inference and shit gets done fine. People don't rely on it. People do rely on for loops.
AsynchronousBureaucraticProccessDispatcherFactoryFactoryFactoryInterfaceProvider
There I fixed it for you. And of course you have to specify the provider in the META-INF/async file.
What's the Go equivalent of "await"? I.e., like the "go" statement but asynchronously wait for the function to return and get its value?
With a single-threaded language using await, it's safer to modify common data structures without locks, although you should be aware that calling a function via "await" gives others task the opportunity to modify your data. So "await" appearing in the code explicitly warns you that pseudo-multitasking is happening. In Go, preemption can (in theory) happen at any time, plus there are multiple threads.
[1] http://honnef.co/posts/2013/08/what_s_happening_in_go_tip__2...
Edit: Go isn't truly preemptive in 1.1.
Comparing callbacks to goto is a tad unfair. They don't merely solve async issues, but also event handling and dynamic systems to name two common uses. I don't see a better solution on the table. Using callbacks to write deeply async code is the real problem. And while async/await may help with this problem, it still won't tell you why step 3 never finishes, because it's still waiting for a come from.
Second: refactorability in callback oriented code is a lot worse than linear code. Even refactoring code with a single callback can be annoying. Async code with callbacks that looks and feels like imperative synchronous code is an enormous gain.
Second: wait until people write ludicrous numbers of async routines as if they were imperative because it's so easy. Calling one from another (and hooking them up to event handlers). You'll have the same damn problem one level removed.
Keep the asynchronous complications at the code that perform non-sequential steps. And yes, I'm fully aware that some libraries (Javascript's one, the guilty are always the same few) force you to use asynchronous calls. That's a flaw of the library.
This article is very interesting - I enjoy articles which spell out the usefulness of a new language feature. I haven't used C# for a few years, and this is a great advert for coming back to it one day.
That being said, I don't think the comparison between callbacks and goto is valid.
"Goto" allows you to create horrible spaghetti-code programs, and getting rid of it forces you to structure your programs better.
"Await", fundamentally, isn't really anything more than syntactic sugar (except for exception handling, which is a good thing). "Await" doesn't change how your program is structured at all, it just changes the visual representation of your code -- from indentations in a non-linear order, to vertical and linear order. It's definitely a nice improvement, and makes code easier to understand (and allows for better exception handling), but it's not actually changing the way your program is fundamentally structured.
And finally, "await" is only applicable when a single callback gets called once at the end. If you're passing a callback that gets used repeatedly (a sorting function, for example), then normal-style callbacks are still necessary, and not harmful at all. Sometimes they can be short lambdas, sometimes they're necessarily much larger.
In sum: "await" is great, but there's nothing inherently harmful about callbacks, the way "goto" is. To the contrary -- callbacks are amazingly useful, and amazingly powerful in languages like JavaScript. "Await" just makes them nicer.
Await does change how your program is structured, unless we're talking about most trivial cases.
You can use `await` inside a `for` loop—can you do the same with callbacks without significantly re-structuring your code?
What is the “callback analog” of placing something in a `finally` block that executes no matter which callback in a nested chain fails? You'd have to repeat that code.
Await has a potential of simplifying the structure a lot, because it befriends asynchronous operations with control flow.
>And finally, "await" is only applicable when a single callback gets called once at the end. If you're passing a callback that gets used repeatedly (a sorting function, for example), then normal-style callbacks are still necessary, and not harmful at all.
Indeed, await is only for the cases when we abuse functions (because we don't know what we'll call, and we have to bend our minds). Passing the sorting comparator is the primary use of first-class functions.
I guess we're using "structured" in different ways. Libraries like TameJS do wind up creating structures to deal with for loops, in the same way you'd otherwise manually have to deal with. Likewise with exceptions (which I said are the main actual benefit to await, that can't be reproduced in normal callback routines).
You obviously have to write a bunch of "plumbing" code to do with "raw" callbacks, in complicated situations (like loops), which "await" does on its own -- and writing that plumbing is annoying, although there are libraries to help.
My only point is, the fundamental structure of your program, on a conceptual level, is still the same. Everything's still running the same way, in the same order. It's just more concise, with less plumbing of your own, using "await". So the micro structure is different with await, but the high-level structure is no different. You can't "abuse" callbacks in the way you can abuse goto. Maybe I should have made that clearer.
[1]: http://praeclarum.org/post/45277337108/await-in-the-land-of-...
I find that async.waterfall + SomeFunction.bind(...) are insanely useful with node.js ... I don't seem to have near the friction in node that I find when working in C# projects.
The genius about them, however, is that with all those, we discovered that you could get rid of "goto" afterwards. Which was kind of amazing.
"Await", on the other hand, doesn't remove the need for callbacks -- it just makes it much easier to use them in a lot of common use cases, in a much clearer way. But there are still plenty of valid/necessary uses for callbacks that can't be handled by "await".
Still, you're right that structured control flow statements have obsoleted goto for all but the tiniest edge cases, and so too do I look forward to callbacks suffering a similar fate at the hands of things like await.
https://www.securecoding.cert.org/confluence/display/seccode...
Skeptics should note that the real-world example on that page is from the Linux kernel, where this style of goto is used heavily for error handling.
https://github.com/bjouhier/galaxy
Essentially await/async using Harmony Generators.
Taking the OP's premise at face value, then, if callbacks are like goto, then await takes away one place where they were needed and replaces them with something more structured and safer---and this doesn't at all negate the possibility that there are other constructs yet to be invented that would continue that process.
In c at least, breaking out of an error condition is still best handled with a goto (where you can clean up all matter of memory in an organized fashion without littering your code with if elses).
It's been nearly a decade since I've used plain C, so I'm not 100% sure, but I was under the impression that it doesn't have any native support for exceptions. So yes, under those circumstances "goto" would definitely be appropriate.
In C#, you can implement resource ownership with the IDisposable interface and "using" keyword, which guarantees that once you leave the block (via an exception or regular control flow), the resource is cleaned up. In C++, you can use the RAII pattern that another commenter brought up. What other problems do exceptions introduce?
In my as-functional-as-is-practical programming philosophy, you throw an exception when there is no valid output for your function. Whether or not that's recoverable is up to the client to decide. Nulls are an extremely poor substitute for this, as they push the responsibility of output validation onto the client, and every single line of code must be enclosed in an "if (foo != null)" block.
EDIT: Removed unproductive "zinger" at end.
As a C++ guy, I don't like his "Busy = false" system to begin with. It would seem much better (to me) if he used a non-copyable object to represent the outstanding activity. Such an object could naturally reset the "Busy" flag in its destructor. But usually there's a better scheme than using a simple Boolean flag to represent a "busy" state anyway. (How is clearing the flag going to release the next guy waiting for the resource?)
So while I'm still a bit skeptical of drawing conclusions from this, I readily admit it's not so off-the-wall as I'd originally thought.
Callbacks really are just like goto. I've seen really awful callback code where you have callbacks that create other callbacks which are passed to callback managers which are themselves finite state automatons and call one of the callbacks based on a return value from another. It's the most horrifying spaghetti code you can think of. It's practically fractal - spaghetti within spaghetti that influence the top layer in an untraceable manner.
While everyone can write spaghetti in any language, few can write good code when given just goto or just lambda. In some cases it's even impossible.
http://blog.barrkel.com/2006/07/fun-with-asynchronous-method...
Await is basically a continuation-passing style transform that puts the continuation in the continue handler of the task. The exception handling is also no more or less syntax sugar than the CPS rewrite - it's an error continuation that needs to be routed by querying the underlying Task's properties.
But the really great thing is that you can finally just thread the async keyword through your call stack to get the CPS effect across multiple method call boundaries, even your case of a sorting function. It's not just applicable for a single callback; the second half, the implementation half, is also implemented, so you're not just limited to using the pattern, but creating new instances easily too.
What is the largest code base you have worked on that used events + callbacks extensively? I worked on a 250,000 lines-of-code Java trading system, and understanding the way the code flow worked from the central event dispatch loop to handler functions, and back to the event dispatch loop took me MONTHS and seriously spoiled the quality of my life (considering how much many hours we spend at work).
Have you used alternative paradigms like Functional Reactive Programming, Promises, etc;? Maybe you don't realize how awesome the alternative is?
I don't feel any respect for the article because it's written on the premise that goto is bad, and that it is anything like a callback.
Callbacks have been, and will continue to be an incredibly useful way to handle events.
I've had some of the exact situations described in the article come up many times at work — especially with blocks in Objective-C (nested error handling, recursive blocks, and so on). The `await` keyword seems like a very nice tool for a lot of common use cases that can be problematic with blocks/callbacks.
http://blog.sigfpe.com/2011/10/quick-and-dirty-reinversion-o...
I found that sometimes it helps to build a state machine to effectively run the show and try to limit callbacks to setting flags and/or navigating the state tree. State machines make following code functionality a breeze, even when dealing with really complex logic.
[1]: http://stackoverflow.com/a/4047607/458193
Callbacks 'get crazy' when you've got more than one I think, and thankfully someone smart has made a library you can use to manage them!
https://github.com/caolan/async
Saying that, I don't mind the way things look with the whole await/async stuff in C# and etc. However I don't think we should be waving our arms around saying callbacks are like goto, they so completely are not! I have written heaps of stuff with callbacks and it's _not that confusing or unmaintainable_. It's just different.
Note that second player is only asked after the first player has given a valid name.
(And the code structure reflects that :-)
My point is of course it's doable with callbacks, but I spent more time indenting this code than writing it, and I darn well know I'm not smart enough to spell out the correct callback-style code in a comment field on Hacker News. And if I suddenly had to add error handling...
was your point that it couldn't be done with callbacks? or couldn't be done easily? or not easily alongside traditional flow control like for loops?
I agree, await and async in C# are very nice, I just took your post as a challenge.
It took me about as long as I typed this code to write it.
Of course it is doable with callbacks, but I know I'm not smart enough to do it in a comment field on HN.
Edit: I was optimistically assuming that the consumer of the many "asks" that are created all at once would process them sequentially, dealing with one and invoking the callback before dealing with the next. If you do not assume this, my problem disappears and you get the simpler problem of spawning many prompts simultaneously.
But to answer your question, since these can't be done in parallel, you'd have to keep track of which player you're asking:
http://github.com/halayli/lthread/
Disclaimer: lthread author
As for the timeouts, it would be strange to bake this into a language (different platforms may support different timers, at the very least).
Instead, you use library for this[1]:
Instead of awaiting on a task, you await on `WhenAny` combinator.[1]: http://stackoverflow.com/a/11191070/458193
[1] http://msdn.microsoft.com/en-us/library/vstudio/hh156528.asp...