Вы смешиваете код и данные. Это очень распространенная ошибка. Например.
(+ 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
или какая-то другая часть вашего кода, которая давала бы клиенту контроль над вашим программным обеспечением.