Определение макроса синтаксиса Clojure

Я определил макрос unless следующим образом:

user=> (defmacro unless [expr body] (list 'if expr nil body))
#'user/unless
user=> (unless (= 1 2) (println "Yo"))
Yo

Как вы можете видеть, он отлично работает.

Теперь в Clojure список можно определить двумя способами:

; create a list
(list 1 2 3)

; shorter notation
'(1 2 3)

Это означает, что макрос unless можно записать без ключевого слова list. Однако это приводит к тому, что исключение Java исключается:

user=> (unless (= 1 2) (println "Yo"))
java.lang.Exception: Unable to resolve symbol: expr in this context

Может кто-нибудь объяснить, почему это не удается?

Ответы

Ответ 1

'(foo bar baz) не является ярлыком для (list foo bar baz), это ярлык для (quote (foo bar baz)). Хотя версия списка вернет список, содержащий значения переменных foo, bar и baz, версия с ' вернет список, содержащий символы foo, bar и baz. (Другими словами, '(if expr nil body) совпадает с (list 'if 'expr 'nil 'body).

Это приводит к ошибке, потому что при цитируемой версии макрос расширяется до (if expr nil body) вместо (if (= 1 2) nil (println "Yo")) (потому что вместо подстановки аргументов макроса для выражения expr и body он просто возвращает имя expr и body (которые затем рассматривается как несуществующие переменные в расширенном коде).

Ярлык, полезный в макроопределениях, использует `. ` работает как ' (т.е. он цитирует выражение, следующее за ним), но он позволяет вам оценивать некоторые подвыражения, не упорядоченные с помощью ~. Например, ваш макрос можно переписать как (defmacro unless [expr body] `(if ~expr nil ~body)). Важно то, что expr и body не закодированы с помощью ~. Таким образом, расширение будет содержать свои значения вместо буквально содержащих имена expr и body.