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

0 votes
in Clojure by
h1. Rationale

When the namespace component of an auto-resolving keyword literal refers to an undeclared alias, the reader produces an error, like so:

user> ::foo/bar
RuntimeException Invalid token: ::foo/bar  clojure.lang.Util.runtimeException (Util.java:221)

For symbols, however, no such syntax exists. This is a common source of errors with functions like {{clojure.spec.test.alpha/instrument}} which accept qualified symbols of var names as arguments. Currently, the go-to notation for such symbol literals is syntax quote which of course accepts any namespace. Now, when one accidentally refers to an alias in one of those symbolic arguments which hasn't been declared in the current namespace (e.g. when transplanting  some code from one namespace to another), it would be very useful to have the same error behavior as with auto-resolving keywords. To stick with the {{instrument}} example, one would end up calling potentially expensive real functions instead of the mocks which could easily go unnoticed.

h1. Suggested syntax

This is a bit tricky. The first thing that comes to mind is double backtick, as in {{``foo/bar}}, but that already has meaning and is likely to already be used in existing macros. I guess {{%foo/bar}} could work, though there is of course no guarantee that this isn't already in use in actual namespace names, too.

h1. Status

As you can tell, I haven't done an extensive survey of possible syntax options, yet, nor have I started working on a patch. Just wanted to solicit opinions on the issue to see if it's worth pursuing any further.

4 Answers

0 votes
_Comment made by: alexmiller_

While I understand your use case, this would be a big syntax addition and I think it’s unlikely. I’m going to Backlog this for now.
0 votes
_Comment made by: dergutemoritz_

Agreed, it's a pretty invasive change probably not worth the trouble. FWIW, in the meantime I figured that the same can be achieved via a tagged literal. In case anyone is looking for a solution, you could have a function like this:

(defn auto-resolved-symbol-reader [sym]
  (let [ns (if-let [alias (namespace sym)]
             (or (get (ns-aliases *ns*) (symbol alias))
                 (throw (ex-info (str "Invalid namespace alias on symbol " sym)
                                 {:symbol sym})))
    (symbol (name (ns-name ns)) (name sym))))

Register it e.g. for the {{sym}} tag by putting it in {{data_readers.clj}} at the root of your classpath:

{sym my.data-readers/auto-resolved-symbol-reader}

And now you can use it to the same effect as {{::foo/bar}} for keywords like this:

'#sym foo/bar
0 votes
_Comment made by: dergutemoritz_

In case of the {{:replace}} argument from the {{instrument}} example, there's another solution that already works. Just use auto-resolved map namespaces with syntax quote like this:

:replace `::foo{bar ~some-fn}

The catch is of course that you need to unquote values now. Note that the syntax quote needs to apply to the whole expression - if it goes in front of the key, the map's namespace won't apply to it because it will already be namespaced by the syntax quote. Actually, one could expect ::foo{'bar some-fn} to also namespace the symbol key but that's not happening. Maybe it only accidentally works with syntax quote after all...
0 votes
Reference: https://clojure.atlassian.net/browse/CLJ-2490 (reported by alex+import)