Welcome! Please see the About page for a little more info on how this works.

+2 votes
in ClojureScript by

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).

2 Answers

+1 vote

This problem has been known for a long time and it's not specific to core.async, every third-party macro performing deep analysis of its body can be impacted. The core.async - specific issues are :
- https://clojure.atlassian.net/browse/ASYNC-91
- https://clojure.atlassian.net/browse/ASYNC-158

What happens is :
- the go macro needs to macroexpand its body to perform its analysis.
- macroexpansion turns and/or into a js* special form wrapping javascript's equivalent boolean operators
- the go macro doesn't know what to do with js* forms so it assumes applicative order because in most situations that's the right thing to do

There's basically two approaches for tackling this problem :
- change the implementation of and/or in clojurescript such that it macroexpands to something less opaque for third-party macro analysis
- require third-party macros to parse the template of the js* special form in order to detect non-applicative-order forms like javascript's boolean operators

Clojure is not a specified language so it's up to the maintainers to decide which way to go. According to the clojurescript team, there's nothing wrong with current implementation of and/or. On the other hand, it's rather overkill to rely on a parser for an ad-hoc unspecified templating language built on top of javascript, just to be able to write remotely correct third-party macros. I've tried to explain the problem several times on slack and the answers I got ranged from "I don't understand what you're talking about" to "It will be low-priority anyways". At this point I doubt anything will be done for this problem in the foreseeable future.

0 votes