๐Ÿ‘คjashkenas๐Ÿ•‘10y๐Ÿ”ผ410๐Ÿ—จ๏ธ146

(Replying to PARENT post)

We can massively generalize this by calling "blue" "pure" and "red" "impure". The end result is essentially Haskell (but you can take it much further, too!).

---

There's something horrifyingly restrictive about having merely (blue -> pure, red -> impure), though. All "blue" functions behave roughly the same (e.g., very, very nicely and compositionally) but "red" functions come in many classes: async, stateful, exception-throwing, non-deterministic, CPS'd, IO-effectful, combinations of the prior.

What we want is a nice way of representing different kinds of red functions and how they all interact.

What we'd also like is a nice way of composing different kinds of red functions so that the bundled piece has a sensible kind of redness too and we can keep composing.

And this is exactly monads and monad transformers.

There are other ways to achieve this end as well all under the general name "Effect Typing". Very cool stuff.

But what I'd like to emphasize is that Java/C#/Go have not solved this larger problem. They each introduce a fixed number of "rednesses" and very specific ways that different kinds of red can be connected together. Monads are a generalizable solution.

The situation is exactly the same as HOFs themselves. We've had subroutines and functions for a long time, but first-order and higher-order functions are a marked increase in power since you can now refer to these "function things" directly.

Monads take the notion of "CPS Transform" and allow you to refer to it directly, to mark sections of code which use it and compose them intelligently. They allow you to invent your own kinds of redness on the fly and ensure that your custom colorations compose just as nicely as the built-in ones.

If this article is even slightly interesting then you owe it to yourself to learn much more about effect typing. It'll change your world.

(That and sum types, because nobody is served by a bunch of integers named Token.LEFT_BRACKET.)

๐Ÿ‘คtel๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

I'm really out on most of the "async" stuff, after having used it. (Mostly in Node and Tornado)

Remember in the early 90s when Windows and Mac OS were "cooperatively" multitasked? Which is to say, you had to explicitly yield to allow other applications to run (or risk locking up the entire system). And then it was replaced with pre-emptive multitasking, which allowed the scheduler to figure out what process deserved CPU time, while allowing the programmer not to have to think about it. You could call a blocking IO function, and the OS would just go do something else while you waited.

All this "async" stuff seems like a return of cooperative multitasking, only worse. Not only do I have to explicitly yield, but now it's to some event loop that can't even properly use multiple cores, or keep a coherent stack trace. It's a nightmare to debug. It's theoretically fast... except if one request forgets to yield, it can clog up the entire thing. I guess you use multiple processes for that and a dispatcher, but at that point you've basically reinvented preemptive multitasking... badly.

Threads aren't perfect, but excluding STM and actor models they definitely suck the least.

๐Ÿ‘คovergard๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

So I've been writing javascript full time for a couple years at this point, client, server, and open source, and what I have adopted is coercing everything into promises, which I suppose would be the author's way of saying making everything red.

If you have something that is not async mixed in with something that's async, you can still add it to the promise chain and it will resolve right away. If you have a library that uses callbacks or some other thing, you can just wrap it such that it now uses promises. And then of course you can always look for alternate libraries that use promises from the start and skip step as well.

I've found that using promises for everything works super well. There is no confusion or doubt at all. Everything has the potential to branch into async at any time with no consequences and without complicating the flow. And an additional benefit is that rather than checking for errors after any operation you do, you choose where to check for errors. When a promise rejects, it skips everything else in the chain until it gets to a catch. So rather than running 4 async operations and doing an "if error do this" after each operation kind of deal, you can catch the error in once place and handle it once. Promises surpress the error in the promise chain until you choose to handle it, which is dangerous if you don't understand how promises work, but really useful once you do.

There are really solid promise-based libraries for all common operations in node right now. When.js for general promises, composition, and coercion, rest.js for network requests, bookshelf and knex for database connection and orm stuff, etc. If you are a js developer, give them a shot!

Don't get me wrong, I'm not trying to claim that this is better than any other language-level construct by any means, but if you are working in javascript, where you have to deal with javascript's limitations as a language, from experience I can say that working in an all-promises environment makes things quite pleasant.

๐Ÿ‘คjenius๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

I had no clue this was about async functions. I assumed it was safe/unsafe functions until I got to the part about it not being. I think that is a much stronger issue than sync/async.
๐Ÿ‘คmalfist๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

A lot of commentors are mentioning that this is just a specific case of effect typing. Haskell and monads have been brought up as an example of effects typing, but I'd like to present another example that more closely resemble familiar static type systems.

Nim[1], at least at one point (I'm looking at the current manual and can't find it documented), had support for tagging functions with a pragma and the compiler would enforce that functions without the pragma can't call function with the pragma outside of a special block. The compiler interpreted certain pragmas like "impure" and "exception" in a special way, outputting a warning when certain language features were used inside functions marked with the pragma. The language manual shows that the compiler still at least supports these special pragmas. It's possible that it never supported custom pragmas and I'm just misremembering.

Interestingly, the author dismisses promises as not a major improvement and calls async/await and generators at least a half-way solution. It turns out that the are just a simple syntactic transform that isn't powerful enough to express everything that coroutines can. Promises, on the other hand, can. Promises are actually a monad: `.then(...)` is the bind function (`>>=`). This is essentially how the IO monad in Haskell works.

[1]: https://nim-lang.org/

๐Ÿ‘คadrusi๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

For those that haven't noticed this article is by the chap who wrote the absolutely wonderful http://gameprogrammingpatterns.com/
๐Ÿ‘คaidos๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

It seems like a lot of people are interested in fixing this, and would be keen to see a solution. I believe StratifiedJS is precisely that solution (for JS at least), and it has existed in working form for years: http://stratifiedjs.org/ (it's not just an experiment - it's remarkably stable).

StratidiedJS completely eliminates the sync/async distinction at the syntax level, with the compiler/runtime managing continuations automatically. A `map` function in SJS works just like you would want, regardless of whether the mapping function is synchronous or not.

In addition, it provides _structured_ forms of concurrency at the language level, as well as cancellation (which most async APIs don't support).

Disclosure: I work on StratifiedJS, and just wish more people knew about it.

๐Ÿ‘คgfxmonk๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Tornado made async code slightly less painful by using yield and coroutines, but you still have to run blocking methods on thread pools using futures. They abstracted it really nicely and I can now write clean code if I need an occasional blocking library in my tornado code.

But after writing tons of Go over the past 2-3 years, going back to async code, even with the tornado sugar, just feels like driving a manual car after getting used to automatic. It's just redundant. I've seen better, I've written way cleaner code and got better concurrency. Promises, futures, yielded generators - they are all syntactic hacks. The only language I've used that really addresses this properly is Go (disclaimer: I haven't written any Erlang).

๐Ÿ‘คdvirsky๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

I don't have anything of substance to add, but author, if you're reading this, I enjoyed your writing style a lot.
๐Ÿ‘คviewer5๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

No actor-model based language has this problem, so perhaps all it comes down to is baking in the right(or even any decent) concurrency support from the start, at the language level.
๐Ÿ‘คecholess๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

[1] is a nice read regarding continuations (and the Cont monad), though a bit more advanced.

[1] http://blog.sigfpe.com/2008/12/mother-of-all-monads.html

๐Ÿ‘คpka๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

His solutions is threads? Really? Has he read no history? There are problems with threads. That's why async I/O is hot right now. Threads is a limited resource. Threads are expensive to create and dispose. Context switches are espensive. Threads must be synchronized. Threads can have race conditions.

Good rant, but I didn't expect him to serve such a shallow conclusion after a solid and insightful introduction.

๐Ÿ‘คNilzor๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

I don't really consider this solved in languages or runtimes that lack green threads. If you want to make 300,000 threads in Lua or Go, go right ahead, but if you port that application to Java you're going to have a bad time.

An orthogonal useful thing that is sometimes not solved in languages with green threads is the ability to copy continuations. If you have call/cc or coroutine.clone, you can e.g. use rollback netcode in your fighting game and store state in execution states, but if you cannot copy execution states, you will have to choose one or the other.

๐Ÿ‘คanonymoushn๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

A related way of composing code is railway oriented programming already commented on HN (1)

Underneath there are monads, sure, but the author has deliberately chosen a more mechanical metaphor which may help those for whom monads sound too abstract.

The post focuses more on how to handle errors that asynchrony, but it shows well the key steps to lift a blue function into red ones and to compose these constructions.

(1) https://news.ycombinator.com/item?id=7887134

๐Ÿ‘คdwenzek๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

I don't get it ... But hey, it took about six month for me to figure out how to write asynchronous JavaScript ... The key is to not use anonymous functions, it will flatten out the "Christmas tree" of callbacks. And it makes it possible to read what the code does, or at least what the programmer wants the code to do. It's much better then "then", then what, but, then, why complicate things when it's actually possible to be verbose.
๐Ÿ‘คz3t4๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

https://glyph.twistedmatrix.com/2014/02/unyielding.html is a good read - there are reasons why explicit sync-async "coloring" (i.e. await/yield) is better than green threads/coroutines which author admires.
๐Ÿ‘คkmike84๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Sadly, you can build a Java API to introduce callback hell if you want to.

https://developer.android.com/reference/android/hardware/cam...

It's nice you don't have to, though.

๐Ÿ‘คfixermark๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Funny. I thought he was talking about Java 8 for a while. We (eventually, when the rest of the stack catches up) get functors / lambdas, but:

* red = instance methods.

* blue = static methods, which cannot call an instance method.

๐Ÿ‘คRoboprog๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Seems like there are couple things going on here. Accusation that asynchronous implementations in all languages are problematic, with which I don't think anyone would disagree. Suggestion that threads are way better than other asynchronous implementations. I don't agree that makes things better or somehow different. You still have disjointed code. I would add this as a third to Fowler's famous quote about two hard things (cache invalidation and naming things).
๐Ÿ‘คhawleyal๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

What about isolates in Dart? I mean isolates are isolated processes, which also can be a thread and they also can communicate with each other.
๐Ÿ‘คStrykerKKD๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

You can always make a function that sync all async operations: sleep until a global variable is changed by the callback.

A pain, but still not that bad.

๐Ÿ‘คtotony๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

  POLLIT
      CMP $C050
      BNE POLLIT
It's not a function, it's a procedure. It doesn't need to return anything, it produces an effect. Interrupts will break it.

   await until an as yet indeterminable time in the future
๐Ÿ‘คmmphosis๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Relevant paper: http://www.info.ucl.ac.be/~pvr/VanRoyChapter.pdf Especially p34+ regarding concurrency paradigms.
๐Ÿ‘คParadigma11๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Interesting allegory. Pretty much the exact same thing could be said about pure/impure functions in a language like Haskell -- where I thought this was going (until I realized it was about JS)
๐Ÿ‘คdmitrig01๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

js-csp enables go-like concurrency in javascript: https://github.com/ubolonton/js-csp
๐Ÿ‘คkarlheinz๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

Or you could be like scheme and make all functions red.
๐Ÿ‘คaidenn0๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0

(Replying to PARENT post)

What about FRP?
๐Ÿ‘คparfamz๐Ÿ•‘10y๐Ÿ”ผ0๐Ÿ—จ๏ธ0