Почему не работает: «Первый аргумент для defn должен быть символом» - PullRequest
0 голосов
/ 01 мая 2018

Почему я получаю ошибку:

IllegalArgumentException First argument to defn must be a symbol  clojure.core/defn (core.clj:277)

Когда я пытаюсь определить функцию следующим образом:

(defn (symbol "f[]") 1)

Или вот так:

(defn (symbol "f") [] 1)

Почему это не эквивалент прямого примера ниже?

(defn f [] 1)

Это эзотерика, которую я знаю, но мне просто пришло в голову, что в какой-то момент я могу захотеть назвать функцию динамически. (Здесь нет реального варианта использования - просто пытаюсь понять мысли Клоюре ...)

Ответы [ 4 ]

0 голосов
/ 01 мая 2018

Вам действительно не нужно использовать eval.

Вы столкнулись с проблемой, известной как " Черепахи, вплоть до ". Когда вы попытаетесь обработать макрос как функцию (например, передав его в map), вы обнаружите, что не можете сделать это без написания другого макроса. То же самое относится к макросу № 2 и т. Д.

Таким образом, вы не можете создавать макросы так же, как вы можете создавать функции. Таков общий совет: «Никогда не используйте макрос, когда вы можете использовать функцию».

В этом случае defn - это макрос, поэтому у вас нет выбора, кроме как написать другой макрос (def ведет себя так же, даже если это специальная форма вместо макроса). Наш новый макрос dyn-defn динамически создает имя функции из списка строк:

(defn fun-1 [] 1)
(def  fun-2 (fn [] 2))

; (def (symbol (str "fun" "-3")) (fn [] 3))
;   => Exception: First argument to def must be a Symbol

(defmacro dyn-defn
  "Construct a function named dynamically from the supplied strings"
  [name-strs & forms]
  (let [name-sym (symbol (str/join name-strs)) ]
    (spyx name-sym)
    `(defn ~name-sym ~@forms)))

(dyn-defn ["fun" "-3"]
  []
  3)

с результатом:

*************** Running tests ***************
:reloading (tst.demo.core)

name-sym => fun-3      ; NOTE:  this is evaluated at compile-time

Testing _bootstrap

-------------------------------------
   Clojure 1.9.0    Java 1.8.0_161
-------------------------------------

Testing demo.core

Testing tst.demo.core

(fun-1) => 1         ; NOTE:  these are all evaluated at run-time
(fun-2) => 2
(fun-3) => 3

Обратите внимание, что имя функции является аргументом макроса defn и должно быть символом, а не вызовом функции.


Примечание:

Правильно, вы не можете сказать, глядя на это, что форма "вызывает" функцию или макрос. Фактически, многие «встроенные» функции Clojure построены из более фундаментальных частей языка, будь то макросы, такие как when ( исходный код ) или функции, подобные into ( исходный код ).

0 голосов
/ 01 мая 2018

Вы смешиваете код и данные. Это очень распространенная ошибка. Например.

(+ 4 5) ; ==> 9
('+ 4 5) ; ==> Error

'+ соответствует символу. не - это то же самое, что и переменная +, которая является кодом и оценивает функцию. Это легко проверить, оценив их:

+  ; ==> #<core$_PLUS_ clojure.core$_PLUS_@312aa7c>
'+ ; ==> +

defn - это макрос, который расширяется до def, поэтому ваша говядина имеет значение def. Причина, по которой (def (symbol "x") 5) не работает, заключается в том, что def происходит во время компиляции. Первые аргументы никогда не оцениваются, но используются для всех ссылок на одни и те же идентификаторы в одном и том же пространстве имен. Выражение типа (symbol "x") не будет работать в значительной степени по той же причине, по которой + и '+ не могут быть смешаны. Вы можете сделать это во время компиляции, хотя:

(defmacro make-fun [name expression]
  `(defn ~(symbol name) [] ~expression))

(macroexpand-1 '(make-fun "f" 1))
; ==> (clojure.core/defn f [] 1)

(make-fun "f" 1)
; ==> #'user/f

(f) ; ==> 1

Итак, до запуска кода (make-fun "f" 1) заменяется на (clojure.core/defn f [] 1), и среда выполнения никогда не видит, откуда он взялся. Хотя это кажется полезным, вы все равно не можете использовать привязку или ввод для выполнения своей функции:

(def fun-name "f")
(def fun-value 1)
(macroexpand-1 '(make-fun fun-name fun-value))
; ==> (clojure.core/defn fun-name [] fun-value)

Макросы - это просто способ упростить и абстрагироваться от синтаксиса. Если вы всегда пишете шаблон, который выглядит как (defn name [& args] (let ...), вы можете создавать части, которые отличаются привязками в макросе, и сокращать каждое место, где вы используете абстракцию с новым макросом. Это служба перевода кода. Во время компиляции аргументы - это просто буквальный код, который предполагается заменить, и вы никогда не сможете себе позволить увидеть, имеет ли переменная или выражение определенное значение, поскольку вы знаете только код, а не то, что они на самом деле представляют. Таким образом, ошибки обычно возникают при выполнении кода в конечном результате.

В конце вы можете делать все что угодно во время выполнения с eval. Я видел, как eval использовался разумным образом дважды за 19 лет работы профессиональным программистом. Вы могли бы сделать:

(defn make-fun [name value]
  (eval `(defn ~(symbol name) [] ~value)))

(make-fun fun-name fun-value)
; #'user/f
(f)
; ==> 1

Теперь, пока это работает, вы не должны делать это, если это не какой-то инструмент для тестирования или выполнения чего-либо с кодом, а не как часть кода, запускаемого как служба со строкой, поступающей из небезопасного источник. Я бы вместо этого выбрал использование словарей, чтобы вы не обновляли свою среду. Представьте, что ввод был make-fun или какая-то другая часть вашего кода, которая давала бы клиенту контроль над вашим программным обеспечением.

0 голосов
/ 01 мая 2018

Ответ - то, что сказал Джош (defn - макрос; если бы это была функция, ваш код действительно работал бы таким образом). Вы можете определить свой собственный вариационный макрос defn, который будет делать то, что вы хотите, или просто использовать eval:

(eval `(defn ~(symbol "f") [] 1))
; => #'user/f
(f)
; => 1
0 голосов
/ 01 мая 2018

Когда вы передаете аргументы макросу, они не оцениваются заранее. Поскольку defn является макросом, то, что вы передаете в этих двух случаях, не эквивалентно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...