Существует ли макрос Clojure, эквивалентный def? - PullRequest
2 голосов
/ 08 июля 2019

Я хочу написать макрос sym-def, который ведет себя так же, как и специальная форма def, но использует (symbol "c"), скажем, в качестве первого аргумента.

Мой первый шаг был

(def (symbol "c") 4)

но это вернуло ошибку First argument to def must be a Symbol.

Мой второй шаг был

(eval `(def ~(symbol "c") 4))

и это позволило определить c как 4 в глобальной среде. Почему мой первый шаг провалился, а второй был успешным?

Наконец, я попытался написать нужный макрос

(defmacro sym-def [sym value] `(def ~sym ~value))

но у этого есть "плохо" macroexpand

(macroexpand '(sym-def (symbol "c") 4)) => (def (symbol "c") 4)

так что

(sym-def (symbol "c") 4)

завершается с той же ошибкой, что и мой первый шаг.

Как правильно написать макрос?

Ответы [ 2 ]

9 голосов
/ 09 июля 2019

def не оценивает свой первый аргумент.Вообразите хаос, если это сделало!Вы не можете написать

(def x 1)

, потому что он сначала попытается оценить x, и потерпит неудачу, потому что x еще не определен!Теперь, поскольку он не оценивает свои аргументы, ясно, что имеет смысл, что

(def (symbol "c") 4)

не работает, так же как

(def 'c 4)

не будет.def требует, чтобы его первый аргумент был буквальным символом.У вас нет буквального символа, поэтому вы не можете использовать def.

Но существует механизм более низкого уровня для взаимодействия с отображениями в пространстве имен.В этом случае вы хотите, чтобы clojure.core/intern:

(intern *ns* (symbol "c") 4)

intern представляла собой обычную функцию, поэтому она оценивает все ее аргументы, а это означает, что вы можете создать свое имя переменной любым сумасшедшим способом.Затем он добавляет к данному пространству имен переменную, отображающую ваш символ на желаемое значение.

2 голосов
/ 11 июля 2019

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

(defmacro sym-def [s v] `(def ~(eval s) ~v))

... или эквивалентно:

(defmacro sym-def [s v] (list `def (eval s) v))

Вам просто нужно оценить первый аргумент внутри макроса, потому что аргументы макроса не оцениваются, когда он применяется. Если вы собираетесь использовать только выражения, производящие символы, это вызовы symbol, вы можете предпочесть следующий макрос:

(defmacro defsym [s v] (list `def (symbol s) v))

... и компаньон к нему:

(defmacro sym [s] (symbol s))

Эти макросы переводят строку или символ в символ. Вот несколько примеров их использования:

(defsym "the first natural number" 0)
;=> #'user/the first natural number
(sym "the first natural number")
;=> 0
(defsym pi 3.14159) ;same as: (def pi 3.14159)
;=> #'user/pi
(sym pi) ;same as: pi
;=> 3.14159

Варианты, приведенные ниже, также могут быть полезны:

(defmacro defsym* [s v] (list `def (symbol (eval s)) v))
(defmacro sym* [s] (symbol (eval s)))

Они переводят строковое / символьное выражение в символ после его оценки. Вот несколько примеров использования defsym*:

(defsym* "abc" "xyz")
(defsym* (str \a \b \c) "xyz")
(defsym* (symbol "abc") "xyz")
(defsym* 'abc "xyz")
;all the previous are equivalent and what follows is valid for any of them
;=> #'user/abc
abc
;=> "xyz"
(defsym* abc 0)
;=> #'user/xyz
abc
;=> "xyz"
xyz
;=> 0
...