It’s no surprise that I don’t like core.async
very much. For starters, it make my functional composition looks like imperative programming again. There’s also multiple issues that you need to be aware of (like, don’t use async/put!
because you will have problems, deadlocks that are difficult to predict, go
blocks don’t compose over functions so you loose lots of helper macros like delay
).
But the most important reason is that most of the time, I’m working in ClojureScript. And it’s impossible to migrate callback to core.async.
Well, you may be tempted to write something like:
(js/someFunction "i'm async" "lol" #(async/put! some-channel %))
And one day or another you’ll have the dreadful Assert failed: No more than 1024 pending puts are allowed error. There are multiple ways around this problem, but none of then work if you can’t lose messages.
The thing is, async code on Javascript is always using callbacks. Even if you use promises (await
is only syntax sugar around .then(<callback>)
. More on this on another post), so things become really complicated really fast.
Now, let’s look at one code from Chlorine: I had to pick up a “var name” (a string), “syntax-quote” it (to return a full qualified name), then run some code on it to get some data, and run another code on it. The problem is that all evaluation is asynchronous. Now, the old code was using go
with promise-chan
, so it was something like this:
(defn find-var-definition [repl ns-name symbol-name] (async/go [result (async/promise-chan) fqn-chan (async/promise-chan) data-chan (async/promise-chan)] (eval/evaluate repl (str "`" symbol-name) {,,,,,} #(async/put! fqn-chan %)) (if-let [fqn (:result (async/<! fqn-chan))] (do (eval/evaluate repl (cmd-for-filename fqn) {,,,,,,} #(async/put! data-chan %)) (get-result repl (async/<! data-chan) result)) (async/put! result {:error true}))) result)
Now, this is way more code that I want: first, because I can’t just “put a nil on it” because this breaks core.async
‘s channels, and second because I’m synchronizing things by hand. Since everything was becoming really hard, I used promesa
to see how easier would be this code:
(defn find-var-definition [repl ns-name symbol-name] (p/let [fqn (eval/eval repl (str "`" symbol-name) {,,,,}) data (eval/eval repl (cmd-for-filename fqn) {,,,,})] (get-result repl data)))
Yep… that’s just it. And I don’t need to send channels to any place. And I don’t need to return promises (so I can return :foo
and it’ll be wrapped around a promise, for example).
Now, even with streams of data like, for example, sockets on node.js, I don’t see core.async
as an option (the latest version of Chlorine is using an atom
and subscribers on it), because if the issue with 1024 pending puts. Also, you need to close the channels after you don’t need them anymore… I feel like I’m back to when I had to manually manage memory and remember to free
pointers.