-
Notifications
You must be signed in to change notification settings - Fork 212
Go Block Best Practices
The go macro stops translating at function creation boundaries. This means the following code will fail to compile, or may just throw a runtime error stating that <!
was used outside of a go block:
(go (let [my-fn (fn [] (<! c))]
(my-fn)))
This is one thing to remember since many Clojure constructs create functions inside macros. The following are examples of code that will not work as one would expect:
(go (map <! some-chan))
(go (for [x xs]
(<! x)))
However other clojure constructs, such as doseq do not allocate closures internally:
; This works just fine
(go (doseq [c cs]
(println (<! c)))
Unfortunately currently there isn't a good way to know if a given macro will work as expected inside a go block unless one either looks a the source, or tests the code generated by the macro.
The best explanation for "why does go block translation stop at function creation?" Basically comes down to a question of types. Examine the following snippet:
(map str [1 2 3])
We can easily see that this produces a seq of strings since the output type of str is a string. So what is the return type of async/<!
. In the context of a go block it is an object taken from a channel. But the go block has to translate that to a parking call to async/put!
. The return type of async/<! should really be thought of as something like Async<Object>
or Promise<Object>
. Thus the result of (map async/<! chans)
is something like "a seq of pending channel operations" which makes no sense at all.
In short, the go macro can't do these operations without some serious work. Other languages such as Erjang
allow for such constructs via translating all code in the entire JVM. This is something we'd like to avoid in core.async, as it complicates things and causes the logic of one library to infect the code of an entire JVM. So we're left with the practical compromise...translation stops when it sees a (fn []...).