Проблема здесь тонкая, и, возможно, ее трудно решить, не разбираясь сначала в макросах.
Макросы управляют синтаксисом так же, как функции управляют значениями. Фактически, макросы - это просто функции с ловушкой, которая заставляет их вычисляться во время компиляции. Им передается литерал данных, который вы видите в исходном коде, и они оцениваются сверху вниз. Давайте создадим функцию и макрос с одним и тем же телом, чтобы вы могли видеть разницу:
(defmacro print-args-m [& args]
(print "Your args:")
(prn args))
(defn print-args-f [& args]
(print "Your args:")
(prn args))
(print-args-m (+ 1 2) (str "hello" " sir!"))
; Your args: ((+ 1 2) (str "hello" " sir!"))
(print-args-f (+ 1 2) (str "hello" " sir!"))
; Your args: (3 "hello sir!")
Макросы заменяются их возвращаемым значением. Вы можете проверить этот процесс с помощью macroexpand
(defmacro defmap [sym & args]
`(def ~sym (hash-map ~@args))) ; I won't explain these crazy symbols here.
; There are plenty of good tutorials around
(macroexpand
'(defmap people
"Steve" {:age 53, :gender :male}
"Agnes" {:age 7, :gender :female}))
; (def people
; (clojure.core/hash-map
; "Steve" {:age 53, :gender :male}
; "Agnes" {:age 7, :gender :female}))
Здесь я, вероятно, должен объяснить, что '
приводит к тому, что следующая форма будет quote
d. Это означает, что компилятор прочитает форму, но не выполнит ее, не попытается разрешить символы и так далее. т.е. 'conj
оценивается как символ, а conj
оценивается как функция. (eval 'conj)
эквивалентно (eval (quote conj))
эквивалентно conj
.
Помня об этом, знайте, что вы не можете разрешить символ как пространство имен, пока он не будет каким-то образом волшебным образом импортирован в ваше пространство имен. Это то, что делает функция require
. Он берет символы и находит пространства имен, которым они соответствуют, делая их доступными в текущем пространстве имен.
Посмотрим, что расширяется до макроса ns
:
(macroexpand
'(ns sample.core
(:require clojure.set clojure.string)))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require 'clojure.set 'clojure.string)))
Видите, как он цитирует нам символы clojure.set
и clojure.string
? Как удобно! Но в чем дело, когда вы используете require
вместо :require
?
(macroexpand
'(ns sample.core
(require clojure.set clojure.string)))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require 'clojure.set 'clojure.string)))
Кажется, что тот, кто написал макрос ns
, был достаточно хорош, чтобы позволить нам делать это обоими способами, поскольку этот результат точно такой же, как и раньше. Neato!
edit: tvachon прав, используя только :require
, поскольку это единственная официально поддерживаемая форма
Но в чем дело со скобками?
(macroexpand
'(ns sample.core
(:require [clojure.set]
[clojure.string])))
; (do
; (clojure.core/in-ns 'sample.core)
; (clojure.core/with-loading-context
; (clojure.core/refer 'clojure.core)
; (clojure.core/require '[clojure.set] '[clojure.string])))
Оказывается, они тоже цитируются, как и мы, если бы писали автономные вызовы require
.
Также оказывается, что ns
не заботится, даем ли мы ему для работы списки (скобки) или векторы (скобки). Он просто рассматривает аргументы как последовательность вещей. Например, это работает:
(ns sample.core
[:gen-class]
[:require [clojure.set]
[clojure.string]])
require
, как указал amalloy в комментариях, имеет разную семантику для векторов и списков, так что не путайте их!
Наконец, почему следующее не работает?
(ns sample.core
(:require 'clojure.string 'clojure.test))
Ну, поскольку ns
цитирует за нас, эти символы цитируются дважды, что семантически отличается от цитирования только один раз, а также является чистым безумием.
conj ; => #<core$conj clojure.core$conj@d62a05c>
'conj ; => conj
''conj ; => (quote conj)
'''conj ; => (quote (quote conj))
Надеюсь, это поможет, и я определенно рекомендую научиться писать макросы. Они супер веселые.
person
d.j.sheldrick
schedule
09.04.2013