Live data from Hacker News

Escape from Callback Hell

ianbishop.github.com

71–80 of 86 posts

Re: Escape from Callback Hell

#71
post #44

Earlier quoted context omitted.

Not conceptually. From the perspective of the rest of the program, isPrime(x) has no side effect regardless of which method you use to implement it. If you are working in a purely functional language, then it makes sense to have introducing IO be a major change, as it has huge potential to make a function impure, and assuming IO is a pure function is roughly the equivalent of unsafePerformIO. However, in this case, w…

Of course it's different. What if the IO fails?

Then the program dies with an exception. What if a memory allocation in a Haskell program fails? It can happen just as well because allocating memory is an impure operation which you still have to perform from pure functions. Haskell just happens to pretend it has infinite memory available because forcing the programmer to attempt handle out of memory conditions would be impractical and annoying. Haskells "purity" is just a chimera, albeit a useful one. And it's just as useful being able to pretend network operations are pure too.

Re: Escape from Callback Hell

#72
post #41

Earlier quoted context omitted.

That isnt the point, its that you have some nice code that does |var x = someBigFunction()| and at some point in the programs life, something inside someBigFunction turns async, you have a whole restructuring to do.

Then that's a breaking change of the someBigFunction()s interface and it should be treated with care, not just quickly patching on a promise and hope for the best. Javascript is single threaded so when you wrote someBigFunction() you didn't have to think of parallel side effects. If you suddenly pause the execution of someBigFunc() and allow the user to mess around with the interface, the global state that someBigFun…

Fair enough. It might be a breaking change but wouldn't it be nice if instead of having to rewrite everything into continuation passing style (a boring and error prone process!) you could just do something simple like

    var a = await someBigFunc()
or

    do
      a 
Javascript in its current state is not built for supporting CPS programming very well. You often end up needing to reimplement control flow primitives like while loops, break statements and exception handling since those don't play nice with async stuff.

Re: Escape from Callback Hell

#73
post #66
post #56

Earlier quoted context omitted.

Do you know if there are any good alternatives that are pure-ish JS? I know that same guy worked on tamejs but they don't seem to be working on that anymore...

There's tame.js from the same guys ( https://github.com/maxtaco/tamejs )

I have heard about tamejs. What I find weird is that 1) the original tamejs website is offline and 2) I never seem to find nice comparisons between the different CPS compilers (I have this problem where I have a hard time using something unless I can know beforehand what sort of "pros and cons" to expect)

Re: Escape from Callback Hell

#74
post #72
post #41

Earlier quoted context omitted.

Then that's a breaking change of the someBigFunction()s interface and it should be treated with care, not just quickly patching on a promise and hope for the best. Javascript is single threaded so when you wrote someBigFunction() you didn't have to think of parallel side effects. If you suddenly pause the execution of someBigFunc() and allow the user to mess around with the interface, the global state that someBigFun…

Fair enough. It might be a breaking change but wouldn't it be nice if instead of having to rewrite everything into continuation passing style (a boring and error prone process!) you could just do something simple like var a = await someBigFunc() or do a Javascript in its current state is not built for supporting CPS programming very well. You often end up needing to reimplement control flow primitives like while loop…

This syntax already exists in LiveScript (http://livescript.net/#backcalls) which compiles to straightforward JS.

    a 

Re: Escape from Callback Hell

#75
post #55
post #43

Earlier quoted context omitted.

> They're just continuations, seriously, what's everyone's problem? That they're badly made continuations, without support from the language. > If you feel like your code is nesting too deep, you define the function elsewhere and just reference it by name. Then you don't get access to the current scope. At the cost of moving stuff out of where it's invoked, so making code harder to read. The problem with callback hel…

My favourite example of this is loops. Its very annoying to put an async call inside the loop since you need to rewrite the loop as a recursive function.

https://github.com/caolan/async

Once I started using the async library, all of these problems everyone is illustrating basically disappeared.

Re: Escape from Callback Hell

#76
post #5

A great library for structuring your callbacks is "async": https://github.com/caolan/async I've only used it with node.js, but it's supposed to work in web browsers as well. It allows you to think a little more procedurally ("waterfall" is especially handy here) while writing CPS code. Very good.

That's my favorite library! I think people who write in node must just see threads like this and laugh.

Re: Escape from Callback Hell

#77
post #2

I honestly find "callback hell" a lot easier to follow and understand than the vast majority of fixes everyone is coming up with. They're just continuations, seriously, what's everyone's problem? You define a function, it gets access to the current scope, it defines the rest of the program flow. If you feel like your code is nesting too deep, you define the function elsewhere and just reference it by name. Then you d…

If they were really continuations they'd be fine. But they're not. They're a broken, terrible approximation of continuations.

A real continuation captures the entire execution context including the current call stack.

Instead what you get in javascript is a stack that's completely meaningless. There's no way to automatically route your exceptions to the real calling context, and there's no automatic way for you to be sure you're seeing all of your callee's exceptions.

If you really want to be sure you'll hear about the ultimate success or failure of an asynchronous call, every single asynchronous step needs to manually install it's own exception handler, trap any exceptions, and pass them back via a failure callback. You're basically doing by hand what the runtime environment is actually supposed to do for you (that being the whole point of exceptions).

Re: Escape from Callback Hell

#78

Earlier quoted context omitted.

That isnt the point, its that you have some nice code that does |var x = someBigFunction()| and at some point in the programs life, something inside someBigFunction turns async, you have a whole restructuring to do.

This is just not something that happens in real life. You know what functions are going to be async up-front, and likely make them async even if you don't know, just in case. Since all IO in js is async by default, having a code that does a simple calculation and introducing IO into it is actually a very big change that warrants the refactoring you'll be required to do.

It's happened to me in real life. When you want to go from keeping a value in external storage to keeping it in memory, you've gone from async to sync. The reverse happens too. These are not huge conceptual changes. When they require restructuring an entire program, it's reasonable to wonder why that program's structure is so brittle. "Likely make them async even if you don't know, just in case" sounds to me like an admission of this problem. Why would I want to make an in-memory lookup async, thus forcing it to wait in the event queue and defeating the purpose of RAM? The only reason to do that is that the programming model imposes a large complexity tax for not doing it.

Consider the simple case where one wants to look up a value synchronously if one has it in memory, and go get it from storage asynchronously if one doesn't. That's a natural thing to want, but it's problematic in Node. The problem is not syntactic—you can easily write a function that calls back immediately in the one case and asynchronously in the other. It's that sync and async semantics don't compose well, so when you start to do anything a little complicated (e.g. launch N requests and call back when all N have either returned or failed), the two trip over one another. Working in a Lisp that compiles to JS, I had to write some surprisingly complex macros in order to get correct behaviour. I wouldn't dream of writing that JS code by hand.

Re: Escape from Callback Hell

#79
post #67
post #64

Earlier quoted context omitted.

Functional programs don't make this easier. And don't say "monad transformer stacks" somehow solve this problem in an easier way.

Haskell definitely makes it easier. You can use preemptive green threads with almost all the ease of cooperatively multitasked code because of the prevalence of immutability. You get the performance benefits of non-blocking code. The simplicity benefits of blocking code. And (almost) none of the threading hell you get in imperative languages.

Are they really preemptive? How does the scheduler decide when to switch?

Re: Escape from Callback Hell

#80

Earlier quoted context omitted.

That isnt the point, its that you have some nice code that does |var x = someBigFunction()| and at some point in the programs life, something inside someBigFunction turns async, you have a whole restructuring to do.

This is just not something that happens in real life. You know what functions are going to be async up-front, and likely make them async even if you don't know, just in case. Since all IO in js is async by default, having a code that does a simple calculation and introducing IO into it is actually a very big change that warrants the refactoring you'll be required to do.

Are you serious? This happens in real life all the time.
Post reply on HN