前几天我遇到了一个非常奇怪的错误。
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)
为假时)。