2

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!

Jeremy Field
  • 652
  • 7
  • 12

2 Answers2

2

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.

Mike Fikes
  • 3,507
  • 14
  • 28
  • Thank you, very clear! Interestingly the dynamic call `(meta ((ns-publics 'project.db) (symbol (name (pred))))` works. I'll try to work out why using what you've explained. – Jeremy Field May 07 '18 at 09:59
  • @JeremyField Yes, using `ns-publics` builds a static map from symbols to vars for a given namespace at compile time. So this approach effectively works (but only for vars in the given namespace), at the cost of dumping all vars in that namespace into the compiled code. In other words, it is a tradeoff with limitations. – Mike Fikes May 07 '18 at 12:00
0

edit: sorry I didn't see that var didn't work for you. Still working on it...

You need to surround the symbol project.db/validation-function with var. This will resolve the symbol to a var.

So what-goes-here? should be

(defn what-goes-here? [pred]
  (var pred))
Ian
  • 98
  • 6
  • 1
    Yes, unfortunately `var` isn't working. It's actually giving "Unable to resolve var: pred in this context" now, not the gensym. Also interestingly I was able to get an arity error by trying to run `(pred)`. It seemed to return `nil` when called correctly though, when it should have returned `true`... Sorry I'm new to lisp. – Jeremy Field May 06 '18 at 16:14
  • I suppose you could create a function that checks if pred is equal to `project.db/validation-function`, and then return the var. `(if (= pred 'project.db/validation-function) #'project.db/validation-function)`. You could then get the meta data off that. You would have to do this for every `var` in the namespace. You could do it dynamically with `(keys (ns-publics project.db)`. – Ian May 06 '18 at 16:53
  • Thank you. Interestingly they compare `=` but not `identical?`. – Jeremy Field May 06 '18 at 20:13