I encountered a very bizarre bug the other day.
The and
macro in clojurescript has an optimization that expands itself something like this (apparently, when it can guarantee that its arguments return booleans -- otherwise, it expands to a use of if
):
(js* "~{} && ~{}" <arg1> <arg2>)
However, in core.async go
blocks, this causes problems because it appears that the codegen hoists the two arguments out of the js*
form, producing something like this in the macroexpanded code:
(let [arg1 <arg1> arg2 <arg2>] (js* "~{} && ~{}" arg1 arg2))
This causes the arguments to and
to be sometimes evaluated eagerly in go blocks (rather than lazily as they should be).
For example, consider this code which checks whether a value is a sequence, and -- if it is -- checks if it's first element is the symbol foo
.
(go (let [x 5] (and (seq? x) (= (first x) 'foo))))
This fails because (seq? x)
and (= (first x) 'foo)
are both detected by the compiler to return booleans, causing the above behavior (expanding to a js*
form rather than if
).
In this case, this throws an exception because (first x)
is invoked on a number (when, (first x)
should not be evaluated because (seq? x)
is false).