= Go Block Best Practices
Timothy Baldridge
2017-05-24
:type: guides
:toc: macro
:icons: font

ifdef::env-github,env-browser[:outfilesuffix: .adoc]

== General advice

It's very tempting to do the following to send a message without waiting for a
reply:
[source,clojure]
----
(go (>! c 42))
----
Although go blocks are cheap, they aren't completely free. Thus it's recommended
to use
[source,clojure]
----
(async/put! c 42)
----
`go` just ends up calling `put!` eventually anyway, so there really isn't a
downside.

Also, if the code is being called inside a callback and you want to respect
back-pressure, it's fairly easy to use a recursive function along with `put!`
to respect back-pressure.
[source,clojure]
----
(defn http-call 
  "Makes an async call to a web browser"
  [url callback] ...)


(def urls [url1 url2 url3])

(defn load-urls 
  "Spools the results of loading several urls onto a channel. 
   does this without creating temporary channels or go blocks"
  [urls out-c]
  (http-call 
    (first urls)
    (fn [response]
      (put! out-c response (fn [_] (load-urls (next urls) out-c))))))

(load-urls urls response-chan)
----

In this example we have some nice clean interop code that allows us to
start working with channels in our app, without creating tons of
channels or gos only to dispose of them shortly after they're created.

Keep in mind that it is important to respect back-pressure. A general
principle in core.async is that unbounded queues are bad and the
number of pending puts is limited (currently to 1024). Another option
is to use a channel with a buffer that always accepts puts immediately,
such as `dropping-buffer` or `sliding-buffer`.

== Unsupported constructs and other limitations in go blocks

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:
[source,clojure]
----
(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:
[source,clojure]
----
(go (map <! some-chan))
(go (for [x xs]
      (<! x)))
----

However, other Clojure constructs, such as `doseq` do not allocate
closures internally:
[source,clojure]
----
; 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
at the source, or tests the code generated by the macro.

== Why is this?

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:
[source,clojure]
----
(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 akin to `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
https://github.com/trifork/erjang[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 [] ...)`.
