Skip to content

Commit 89b2f15

Browse files
authored
Merge pull request #178 from slipset/86
Add go block advice page into guide, fixes #86
2 parents 93cf063 + 64b723f commit 89b2f15

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

content/guides/core_async_go.adoc

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
= Go Block Best Practices
2+
Timothy Baldridge
3+
:type: guides
4+
:toc: macro
5+
:icons: font
6+
7+
ifdef::env-github,env-browser[:outfilesuffix: .adoc]
8+
9+
== General advice
10+
11+
It's very tempting to do the following to send a message without waiting for a
12+
reply:
13+
[source,clojure]
14+
----
15+
(go (>! c 42))
16+
----
17+
Although go blocks are cheap, they aren't completely free. Thus it's recommended
18+
to use
19+
[source,clojure]
20+
----
21+
(async/put! c 42)
22+
----
23+
`go` just ends up calling `put!` eventually anyway, so there really isn't a
24+
downside.
25+
26+
Also, if the code is being called inside a callback and you want to respect
27+
back-pressure, it's fairly easy to use a recursive function along with `put!`
28+
to respect back-pressure.
29+
[source,clojure]
30+
----
31+
(defn http-call
32+
"Makes an async call to a web browser"
33+
[url callback] ...)
34+
35+
36+
(def urls [url1 url2 url3])
37+
38+
(defn load-urls
39+
"Spools the results of loading several urls onto a channel.
40+
does this without creating temporary channels or go blocks"
41+
[urls out-c]
42+
(http-call
43+
(first urls)
44+
(fn [response]
45+
(put! out-c response (fn [_] (load-urls (next urls) out-c))))))
46+
47+
(load-urls urls)
48+
----
49+
50+
In this example we have some nice clean interop code that allows us to
51+
start working with channels in our app, without creating tons of
52+
channels or gos only to dispose of them shortly after they're created.
53+
54+
== Unsupported constructs and other limitations in go blocks
55+
56+
The go macro stops translating at function creation boundaries. This
57+
means the following code will fail to compile, or may just throw a
58+
runtime error stating that `<!` was used outside of a go block:
59+
[source,clojure]
60+
----
61+
(go (let [my-fn (fn [] (<! c))] (my-fn)))
62+
----
63+
64+
This is one thing to remember since many Clojure constructs create
65+
functions inside macros. The following are examples of code that will
66+
not work as one would expect:
67+
[source,clojure]
68+
----
69+
(go (map <! some-chan))
70+
(go (for [x xs]
71+
(<! x)))
72+
----
73+
74+
However, other Clojure constructs, such as `doseq` do not allocate
75+
closures internally:
76+
[source,clojure]
77+
----
78+
; This works just fine
79+
(go (doseq [c cs]
80+
(println (<! c)))
81+
----
82+
83+
Unfortunately, currently there isn't a good way to know if a given
84+
macro will work as expected inside a go block unless one either looks
85+
at the source, or tests the code generated by the macro.
86+
87+
== Why is this?
88+
89+
The best explanation for "why does go block translation stop at
90+
function creation?" basically comes down to a question of
91+
types. Examine the following snippet:
92+
[source,clojure]
93+
----
94+
(map str [1 2 3])
95+
----
96+
97+
We can easily see that this produces a `seq` of strings since the
98+
output type of `str` is a string. So what is the return type of
99+
`async/<!`? In the context of a go block it is an object taken from a
100+
channel. But the go block has to translate that to a parking call to
101+
`async/put!`. The return type of `async/<!` should really be thought
102+
of as something akin to `Async<Object>` or `Promise<Object>`. Thus the
103+
result of `(map async/<! chans)` is something like "a seq of pending
104+
channel operations" which makes no sense at all.
105+
106+
In short, the go macro can't do these operations without some serious
107+
work. Other languages such as
108+
https://github.com/trifork/erjang[Erjang], allow for such constructs
109+
via translating all code in the entire JVM. This is something we'd
110+
like to avoid in core.async, as it complicates things and causes the
111+
logic of one library to infect the code of an entire JVM. So we're
112+
left with the practical compromise, translation stops when it sees a
113+
`(fn [] ...)`.

content/guides/guides.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ifdef::env-github,env-browser[:outfilesuffix: .adoc]
1515
* <<threading_macros#,Threading Macros>>
1616
* <<comparators#,Comparators>>
1717
* <<reader_conditionals#,Reader Conditionals>>
18+
* <<core_async_go#,Go Block Best Practices>>
1819

1920
=== Tools
2021

0 commit comments

Comments
 (0)