Статьи

Clojure: & env и & form

Внутри тела 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. Я думаю, вы никогда не знаете, что вам нужно …

С http://blog.jayfields.com/2011/02/clojure-and.html