Как извлечь метаданные из переменной, когда функция возвращает свой символ?

Я использую re-frame с spec для проверки app-db, как в todomvc.

Когда пользователь делает недопустимую запись, я использую s/explain-data (в перехватчике повторного кадра), чтобы вернуть карту problems с именем :predicate, которое вызвало ошибку проверки. Этот предикат является символом, подобным project.db/validation-function.

Моя функция проверки имеет метаданные, которые доступны из repl, используя:

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

Определение функции (в пространстве имен project.db) выглядит так:

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

Проблема в том, что я не могу понять, как динамически извлекать метаданные (работая в пространстве имен project.events), например:

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

Я пробовал следующие вещи вместо what-goes-here?:

  • symbol? дает true
  • str дает "project.db/validation-function"
  • meta дает nil
  • var выдает ошибку времени компиляции «Невозможно разрешить var: p1__46744# в этом контексте»

Я думаю, проблема в том, что я получаю символ, но мне нужна переменная, на которую он ссылается, и именно там живут метаданные.

Я пробовал использовать макрос, но действительно не знаю, что я делаю. Это самое близкое обсуждение, которое я смог найти, но я не смог разобраться.

Помощь!


person Jeremy Field    schedule 06.05.2018    source источник


Ответы (2)


В общем, вы не можете этого сделать, потому что переменные не овеществлены в ClojureScript.

Из 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.)

В REPL, когда вы оцениваете

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

это то же самое, что

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

и когда (var project.db/validation-function) компилируется, генерируется код JavaScript для создания экземпляра cljs.core/Var, который содержит, среди прочего, данные, которые вы можете получить с помощью meta. Если вам интересно, соответствующий анализатор и компилятор поучителен.

Таким образом, если (var project.db/validation-function) (или эквивалент #'project.db/validation-function для чтения) не существует нигде в вашем исходном коде (или косвенно через использование чего-то вроде ns-publics), эти данные не будут доступны во время выполнения.

Отсутствие реификации var полезно при оптимизации размера кода. Если вы включите параметр REPL :repl-verbose, вы увидите, что выражение (var project.db/validation-function) генерирует значительный объем кода JavaScript.

При работе с defs в REPL компилятор содержит достаточное количество метаданных для анализа, и такие действия выполняются — например, когда оценки def форм возвращают переменную, а не значение — во имя создания иллюзии того, что вы работаете с овеществленными переменными Clojure. Но эта иллюзия намеренно испаряется при создании кода для промышленной поставки, сохраняя только существенное поведение во время выполнения.

person Mike Fikes    schedule 07.05.2018
comment
Спасибо, очень понятно! Интересно работает динамический вызов (meta ((ns-publics 'project.db) (symbol (name (pred)))). Я попытаюсь понять, почему, используя то, что вы объяснили. - person Jeremy Field; 07.05.2018
comment
@JeremyField Да, использование ns-publics создает статическую карту из символов в переменные для заданного пространства имен во время компиляции. Таким образом, этот подход эффективно работает (но только для переменных в данном пространстве имен) за счет сброса всех переменных в этом пространстве имен в скомпилированный код. Другими словами, это компромисс с ограничениями. - person Mike Fikes; 07.05.2018

изменить: извините, я не видел, что var не работает для вас. Все еще работаю над этим...

Вам нужно окружить символ project.db/validation-function var. Это разрешит символ в var.

Так что what-goes-here? должно быть

(defn what-goes-here? [pred]
  (var pred))
person Ian    schedule 06.05.2018
comment
Да, к сожалению, var не работает. На самом деле теперь в этом контексте выдается Невозможно разрешить var: pred, а не gensym. Также интересно, что я смог получить ошибку арности, пытаясь запустить (pred). Казалось, что при правильном вызове он возвращает nil, хотя должен был возвращать true... Извините, я новичок в lisp. - person Jeremy Field; 06.05.2018
comment
Я полагаю, вы могли бы создать функцию, которая проверяет, равно ли значение pred project.db/validation-function, а затем возвращает значение var. (if (= pred 'project.db/validation-function) #'project.db/validation-function). Затем вы можете получить метаданные. Вам придется сделать это для каждого var в пространстве имен. Вы можете сделать это динамически с помощью (keys (ns-publics project.db). - person Ian; 06.05.2018
comment
Спасибо. Интересно, что они сравнивают =, но не identical?. - person Jeremy Field; 06.05.2018