Search code examples
clojurescriptclojure.specre-frame

How do I extract metadata from a var when a function returns its symbol?


I'm using re-frame with spec to validate app-db, much like in the todomvc example.

When a user makes an invalid entry, I'm using s/explain-data (in a re-frame interceptor) to return a problems map naming the :predicate which caused validation failure. This predicate is a symbol like project.db/validation-function.

My validation function has metadata which is accessible from the repl using:

(meta #'project.db/validation-function)

The function definition (in the project.db namespace) looks like this:

(defn validation-function
  "docstring..."
  {:error-message "error message"}
  [param]
  (function-body...)

The problem is I can't work out how to retrieve the metadata dynamically (working in project.events namespace), for example:

(let [explain-data (s/explain-data spec db)
      pred (->> (:cljs.spec.alpha/problems explain-data) first :pred)
      msg (what-goes-here? pred)]
  msg)

I've tried the following things in place of what-goes-here?:

  • symbol? gives true
  • str gives "project.db/validation-function"
  • meta gives nil
  • var gives a compile-time error "Unable to resolve var: p1__46744# in this context"

I think the problem is that I'm getting a symbol, but I need the var it refers to, which is where the metadata lives.

I've tried using a macro, but don't really know what I'm doing. This is the closest discussion I could find, but I couldn't work it out.

Help!


Solution

  • In general, you can't do this because vars are not reified in ClojureScript.

    From https://clojurescript.org/about/differences#_special_forms :

    • var notes
      • Vars are not reified at runtime. When the compiler encounters the var special form it emits a Var instance reflecting compile time metadata. (This satisfies many common static use cases.)

    At the REPL, when you evaluate

    (meta #'project.db/validation-function)
    

    this is the same as

    (meta (var project.db/validation-function))
    

    and when (var project.db/validation-function) is compiled, JavaScript code is emitted to create a cljs.core/Var instance that contains, among other things, the data that you can obtain using meta. If you are curious, the relevant analyzer and compiler code is instructive.

    So, if (var project.db/validation-function) (or the reader equivalent #'project.db/validation-function) doesn't exist anywhere in your source code (or indirectly via the use of something like ns-publics) this data won't be available at runtime.

    The omission of var reification is a good thing when optimizing for code size. If you enable the :repl-verbose REPL option, you will see that the expression (var project.db/validation-function) emits a significant amount of JavaScript code.

    When working with defs at the REPL, the compiler carries sufficient analysis metadata, and things are done—like having evaluations of def forms return the var rather than the value—in the name of constructing an illusion that you are working with reified Clojure vars. But this illusion intentionally evaporates when producing code for production delivery, preserving only essential runtime behavior.