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

0 votes
in Syntax and reader by

edn/read's :readers arg accepts a map of tag symbols to data-reader functions, but *data-readers* requires a map of tag symbols to data-reader Vars (global bindings). I'm just wondering if there is any reason for the discrepancy, because it is a bummer not to be able to use anonymous functions in *data-readers*.

The context is I've made a handful of related reader tags (for tagged literals) that all work similarly, so rather than create 5 near identical reader functions I made 1, with two arity (tag, value), then I made a reader-for function that returns a one-arity function as needed (using partial and the two-arity function). This works just fine for edn/read but when I went to use it in *data-readers* it's a no-go because there is no Var for what comes out of reader-for.

There are several workarounds. For example bind *default-data-reader-fn*and make a function to dispatch to all my readers. Which is not particularly difficult but now I'm left with two artifacts instead of one - the map of readers for edn/read and the function for *default-data-reader-fn*. The map can be leveraged in the function of course. Or as an alternative I can just write the five functions -- or "write" them in a doseq. But the little inconsistencies across how these tags are done is a little baffling. (For example, in addition to the {tag-sym fn} map of :reader and the {tag-sym Var} map of *data-readers*, there is also the {unquoted-tag-sym-in-a-map-literal fn-sym} syntax of data_readers.clj, which despite being a clj file does not seem to afford me the opportunity to do anything dynamic to define readers, just the literal map).

I probably just don't grasp the practicalities/history/logic behind all this, if anyone can shed some light thanks in advance, if not that's fine too.

1 Answer

0 votes

I was not in the core team when this was developed so I am guessing a bit, but I presume that *data-readers* is intentionally related to the structure of data_readers.clj.

For data_readers.clj, the intent there is to let you connect a tag to a reader function in code that must be loaded. This file is .clj rather than .edn because data readers were added in Clojure 1.4 and the edn spec had not yet been written (that came out later that year).

A long-standing problem that many experienced Clojurists have probably encountered is that there is no standard way, in edn, to represent a reference to an invokable code reference, like a var. You are stuck with at best sending symbols and resolving them when needed to recover the Var. Over the last couple of releases we have inched towards a solution to that problem - Var serialization (in Java) now reconnects to the local implementation on deserialization, requiring-resolve was added and helps with the symbol side. The next probable step is to make vars serializable to edn (#var my/fn) and then creating a built-in data reader to deserialize into the resolved Var. If we had had all that, it probably would have been data_readers.edn with vals that were var tagged literals, not symbols.

I guess my real question is whether you can go one level deeper - what is the context why do you need to monkey with *data-readers*? What is the context where you're not able to inject the data reader map you want directly into the reader?