前几天我遇到了一个非常奇怪的bug。
clojurescript中的and
宏有一个优化,它将自身展开成类似这样(显然,当它可以保证其参数返回布尔值时——否则,它会展开为if
的使用)。
(js* "~{} && ~{}" <arg1> <arg2>)
然而,在core.async的go
块中,这会产生问题,因为代码生成器会将两个参数提升出js*
形式,在宏展开的代码中产生类似这样
(let [arg1 <arg1> arg2 <arg2>] (js* "~{} && ~{}" arg1 arg2))
这会导致在go块中有时会*谨慎地*评估and
的参数(而不是像应该那样懒加载)。
例如,考虑这个代码,该代码检查一个值是否是序列,如果是——检查其第一个元素是否是符号foo
。
(go (let [x 5] (and (seq? x) (= (first x) 'foo))))
这失败了,因为编译器检测到(seq? x)
和(= (first x) 'foo)
都返回布尔值,导致上述行为(展开成js*
形式而不是if
)。
在这种情况下,这抛出了一个异常,因为对数字调用了(first x)
(此时(first x)
应该不被评估,因为(seq? x)
为假)。