Clojure / Clojurescript: аргумент для разрешения должен быть в кавычках - PullRequest
0 голосов
/ 17 января 2019

Я пытаюсь использовать строку, сохраненную в переменной, для вызова функции следующим образом:

(defn update-product-list [] "Test")

(defn handle-state-change [action]
  ((resolve (symbol action))))

(handle-state-change "update-product-list")

Однако, это дает мне следующую ошибку: Утверждение не удалось: аргумент для разрешения должен быть символом в кавычках

Я также попытался изменить вышеприведенную строку на:

((resolve (quote (symbol action))))

Но это все равно дает ошибку. Я также попытался изменить это просто:

((resolve 'action))

Но это дает другую ошибку, которую я не совсем понимаю: js / action затеняется локальным. Я не хочу переопределять функцию, просто вызовите ее. Не уверен, где я иду не так. Я посмотрел на несколько примеров, но не вижу, как это закрепить.

Ответы [ 2 ]

0 голосов
/ 17 января 2019

ClojureScript поддерживает оптимизацию :advanced, при которой Google Closure Compiler будет переименовывать, вставлять или исключать (неиспользуемые) функции для реализации минимизации. Короче говоря, имя функции, которую вы хотите найти, в общем случае просто больше не будет существовать в :advanced.

Из-за этого ClojureScript resolve является средством времени компиляции (макрос, требующий буквально заключенного в кавычки символа).

Если вы используете :simple или самодостаточный ClojureScript, вам доступны дополнительные параметры, поскольку необходимая поддержка сохраняется во время выполнения. Например, Планка имеет planck.core/resolve, который ведет себя как Clojure resolve. Подобный подход возможен в Lumo , и аналогичные средства могут быть созданы при использовании :simple.

В общем, хотя, учитывая :advanced, если вам нужно отобразить строки в набор функций, вам нужно каким-то образом организовать статическое отображение, построенное во время компиляции для поддержки этого (набор функций должен быть известен a priori , во время компиляции).

Если у вас есть пространство имен (имя которого статически известно во время компиляции), которое определяет функции, которые должны динамически вызываться через строки, вы можете рассмотреть возможность использования ns-publics:

cljs.user=> (ns foo.core)

foo.core=> (defn square [x] (* x x))
#'foo.core/square
foo.core=> (in-ns 'cljs.user)
nil
cljs.user=> (when-some [fn-var ((ns-publics 'foo.core) (symbol "square"))]
               (fn-var 3))
9

Это будет работать под :advanced. Отображение, построенное с помощью ns-publics, является статическим; построен во время компиляции. Если у вас есть несколько пространств имен, требующих такой обработки, вы можете merge несколько вызовов ns-publics, чтобы построить большую карту.

Преимущество этого подхода заключается в том, что используемый код довольно короткий и не требует значительного обслуживания. Недостатком является то, что он сбрасывает все открытые переменные пространства имен (foo.core в этом примере) в ваш сгенерированный код (и сгенерированный код для vars несколько многословен). Другим недостатком является то, что вам необходимо статически знать пространство имен, задействованное во время компиляции.

Если вам нужно еще больше минимизировать размер сгенерированного кода, вы можете просто создать / поддерживать простую статическую карту из строки в значение функции, как в

(def fns {"square" foo.core/square})

и используйте его надлежащим образом, обновляя его по мере развития вашей кодовой базы.

Другой вариант - пометить функции, к которым вам нужно получить доступ, с помощью ^:export meta, а затем вызвать эти функции с помощью взаимодействия JavaScript. Например, если вы определяете функцию таким образом

 (defn ^:export square [x] (* x x))

тогда вы можете использовать strings / interop для поиска функции и вызова ее во время выполнения. Вот пример:

 ((goog.object/getValueByKeys js/window #js ["foo" "core" "square"]) 3)

Использование ^:export и :advanced покрыто здесь . Если вы знаете, что используете :simple или меньше, вы можете просто использовать взаимодействие JavaScript для вызова интересующих функций, без необходимости использовать ^:export.

Обратите внимание, что не существует общего решения, которое позволило бы вам искать функцию по имени во время выполнения в :advanced, не добавляя какой-либо аспект этой функции в ваш код во время компиляции. (Фактически, если на функцию не ссылаются так, как статически компилятор Google Closure может видеть, реализация функции будет полностью исключена как мертвый код.) В приведенном выше примере ns-publics захватывает все переменные для пространства имен при компиляции время, когда вы катите свою собственную карту поиска, устанавливает статический код для ссылки на значение функции и использует ^:export статически, чтобы имя функции сохранялось во время выполнения.

0 голосов
/ 17 января 2019

Вам нужно использовать это так:

((resolve 'inc)  5))  => 6

или, немного деконструировано:

(let [the-fn (resolve 'inc)]
   (the-fn 7))

=> 8

Если у вас есть имя функции в виде строки, используйте функцию symbol для преобразования из символа string => ( из clojuredocs.org ):

user=> ((-> "first" symbol resolve) [1 2 3])
1

И никогда не забывайте Шпаргалка Clojure!

...