Question

I have been trying to use Clojure tagged literals, and noticed that the reader does not evaluate the arguments, very much like a macro. This makes sense, but what is the appropriate solution for doing this? Explicit eval?

Example: given this function

(defn my-data
  ([[arg]]
     (prn (symbol? arg))
     :ok))

and this definition data_readers.clj

{myns/my-data my.ns/my-data}

The following behave differently:

> (let [x 1] (my.ns/my-data [x]))
false
:ok

So the x passed in is evaluated before being passed into my-data. On the other hand:

> (let [x 1] #myns/my-data [x])
true
:ok

So if I want to use the value of x inside my-data, the my-data function needs to do something about it, namely check that x is a symbol, and if so, use (eval x). That seems ugly. Is there a better approach?

Was it helpful?

Solution

Summary

There is no way to get at the value of the local x in your example, primarily because locals only get assigned values at runtime, whereas tagged literals are handled at read time. (There's also compile time in between; it is impossible to get at locals' values at compile time; therefore macros cannot get at locals' values either.)1

The better approach is to use a regular function at runtime, since after all you want to construct a value based on the runtime values of some parameters. Tagged literals really are literals and should be used as such.

Extended discussion

To illustrate the issue described above:

(binding [*data-readers* {'bar (fn [_] (java.util.Date.))}]
  (eval (read-string "(defn foo [] #bar x)")))

foo will always return the same value, because the reader has only one opportunity to return a value for #bar x which is then baked into foo's bytecode.

Notice also that instead of passing it to eval directly, we could store the data structure returned by the call to read-string and compile it at an arbitrary point in the future; the value returned by foo would remain the same. Clearly there's no way for such a literal value to depend on the future values of any locals; in fact, during the reader's operation, it is not even clear which symbols will come to name locals -- that's for the compiler to determine and the result of that determination may be non-obvious in cases where macros are involved.

Of course the reader is free to return a form which looks like a function call, the name of a local etc. To show one example:

(binding [*data-readers* {'bar (fn [sym] (list sym 1 2 3 4 5))}]
  (eval (read-string "#bar *")))
;= 120
;; substituting + for * in the string yields a value of 15

Here #bar f becomes equivalent to (f 1 2 3 4 5). Needless to say, this is an abuse of notation and doesn't really do what you asked for.


1 It's worth pointing out that eval has no access to locals (it always operates in the global scope), but the issue with locals not having values assigned before runtime is more fundamental.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top