Внутри тела defmacro вы можете вызывать & env и & form, чтобы получить немного интересной информации, которая может быть полезной или не полезной.
Вот несколько примеров, которые демонстрируют, как & env и & form могут быть использованы.
(примечание: я использую Clojure 1.2)
& env
По умолчанию & env равно nil.
user=> (defmacro show-env [] (println &env))
#'user/show-env
user=> (show-env)
nil
Однако, если существуют какие-либо привязки, & env выдает имена привязок в качестве ключей карты.
user=> (let [band "zeppelin" city "london"] (show-env))
{city #<LocalBinding clojure.lang.Compiler$LocalBinding@78ff9053>, band #<LocalBinding clojure.lang.Compiler$LocalBinding@525c7734>}
Хорошо, теперь мы куда-то добираемся. Что такое компилятор $ LocalBinding? Я не совсем уверен, и я никогда не удосужился разобраться в этом. Мне сказали, что «значения» из & env могут измениться в будущем, поэтому я бы не стал полагаться на них в любом случае. Поскольку я не могу положиться на них, я не нашел необходимости изучать их.
Вернуться к ключам. Они уверены, что выглядят как символы.
user=> (defmacro show-env [] (println (keys &env)) (println (map class (keys &env))))
#'user/show-env
user=> (let [band "zeppelin" city "london"] (show-env))
(city band)
(clojure.lang.Symbol clojure.lang.Symbol)
Как показывает пример, они определенно являются символами. Однако эти символы не имеют пространства имен.
user=> (defmacro show-env [] (println (keys &env)) (println (map namespace (keys &env))))
#'user/show-env
user=> (let [band "zeppelin" city "london"] (show-env))
(city band)
(nil nil)
Так как у символов нет пространства имен, мне было не очень весело с ними; однако вы можете использовать символы в макросе для печати значений, как показано в следующем примере.
user=> (defmacro show-env [] (println (keys &env)) `(println ~@(keys &env)))
#'user/show-env
user=> (let [band "zeppelin" city "london"] (show-env))
(city band)
london zeppelin
Печать значений привязок может быть полезной при отладке.
& form
& form можно использовать для получения исходного вызова макроса.
user=> (defmacro show-form [] (println &form))
#'user/show-form
user=> (show-form)
(show-form)
Ладно, пока не очень интересно. Это становится немного интереснее, когда ваш макрос принимает несколько аргументов.
user=> (defmacro show-form [a b] (println &form))
#'user/show-form
user=> (show-form 50 100)
(show-form 50 100)
user=> (show-form a 100)
(show-form a 100)
Таким образом, вы можете получить аргументы. Обратите внимание, что вы можете взять как 50, так и 100.
user=> (defmacro show-form [a b] (println (next &form)))
#'user/show-form
user=> (show-form 50 100)
(50 100)
user=> (defmacro show-form [a b] (println (map class (next &form))))
#'user/show-form
user=> (show-form 50 100)
(java.lang.Integer java.lang.Integer)
Интересный. У меня есть несколько целых чисел, с которыми я могу работать, если захочу. А как насчет «шоу-формы»?
user=> (defmacro show-form [a b] (println (map class &form)))
#'user/show-form
user=> (show-form 50 100)
(clojure.lang.Symbol java.lang.Integer java.lang.Integer)
«Шоу-форма» является символом, как и ожидалось. Что возвращает нас к предыдущему примеру, который снова показан ниже.
user=> (defmacro show-form [a b] (println (map class &form)))
#'user/show-form
user=> (show-form a 100)
(clojure.lang.Symbol clojure.lang.Symbol java.lang.Integer)
Хорошо, «а» также является символом, что неудивительно, но, возможно, это интересно, поскольку «а» не существует нигде, кроме как в нашем вызове. Вы, вероятно, можете сделать некоторые интересные вещи здесь, например, позволить людям указывать значения enum и добавлять enum самостоятельно.
user=> (ns user (:import [java.util.concurrent TimeUnit]))
java.util.concurrent.TimeUnit
user=> (defmacro time-units [& l] (->> (next &form) (map (partial str "TimeUnit/")) (map symbol) (cons 'list)))
#'user/time-units
user=> (time-units SECONDS MILLISECONDS)
(#< SECONDS> #< MILLISECONDS>)
Хотели бы вы использовать & форму вместо использования аргументов (хранящихся в l)? Возможно нет. Это не упражнение в том, что вы должны делать, но оно демонстрирует, что вы могли бы сделать.
Итак, форма должна возвращать список, верно?
user=> (defmacro show-form [a b] (println (map class &form)) (println (class &form)))
#'user/show-form
user=> (show-form 50 100)
(clojure.lang.Symbol java.lang.Integer java.lang.Integer)
clojure.lang.PersistentList
Список. Верный.
И я в макросе, поэтому я могу делать все, что захочу с этим списком. Может быть, я просто хочу напечатать аргументы. Достаточно просто.
user=> (defmacro show-form [a b] `(println ~@(next &form)))
#'user/show-form
user=> (show-form 50 100)
50 100
Конечно, я мог бы сделать миллион вещей. Вы поняли идею.
Еще одна интересная вещь, на которую следует обратить внимание, это то, что & form имеет метаданные
user=> (show-form 50 100)
{:line 132}
Возможно, вам не важны номера строк, но они определенно могут пригодиться, когда вы
пишете среду тестирования .
Я использую & форму в ожиданиях, и я считаю, что
LazyTest использует & env. Я думаю, вы никогда не знаете, что вам нужно …