Clojure

Add symbol literal syntax with the same alias resolving behavior as auto-resolved keywords

Details

  • Type: Feature Feature
  • Status: Open Open
  • Priority: Minor Minor
  • Resolution: Unresolved
  • Affects Version/s: Release 1.10
  • Fix Version/s: Backlog
  • Component/s: None
  • Labels:
    None

Description

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.

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.

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.

Activity

Hide
Alex Miller added a comment -

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.

Show
Alex Miller added a comment - 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.
Hide
Moritz Heidkamp added a comment - - edited

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})))
             *ns*)]
    (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
Show
Moritz Heidkamp added a comment - - edited 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})))
             *ns*)]
    (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
Hide
Moritz Heidkamp added a comment -

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

Show
Moritz Heidkamp added a comment - 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...

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated: