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