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

+1 vote
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

+2 votes
selected by
Best answer

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?

Thanks, Alex, I really appreciated the thorough explanation of the history here, which makes sense of things. To answer your question about why I was monkeying with *data-readers*, I don't think I understood at the time that data_readers.clj did everything I needed to do. For example it wasn't clear to me at the time that this file can be distributed with libraries and will work fine (I thought it was just something individual users made). I also didn't realize it works fine in the REPL, possibly because it took me a bit to get the syntax correct and in the meantime it wasn't loading in the REPL. (I was mis-naming my tags like namespaces, "foo.bar.baz", instead of like symbols, "foo.bar/baz", although I can't recall if this was an issue with data_readers.clj or just *data-readers*. It worked fine with edn/read to name tags that way so it took me a bit to realize what I was doing incorrectly.)

Anyway, it turns out I do not need to monkey with *data-readers*. Thank you again for the thoughtful reply.