Скажем, я хочу создать макрос Clojure, который выполняет следующие действия:
If x is a list calling the function "bar"
return :foobar
else
return x as a string
Однако bar
не определено; скорее он используется только внутри макроса, например:
(foo (bar))
:foobar
(foo 1)
"1"
Можно сделать что-то вроде этого:
(defmacro foo [x]
(if (and (coll? x) (= (first x) 'bar))
:foobar
(str x)))
Это прекрасно работает для случая (bar)
, а также для литералов. Однако символы не работают должным образом, давая имя символа вместо его соответствующего значения:
user=> (def y 2)
#'user/y
user=> (foo y)
"y"
Можно вызвать функцию eval
на x
перед передачей ее на str
, но это вызывает проблемы при использовании функции в let
:
user=> (let [a 3 b (foo a)] b)
java.lang.UnsupportedOperationException: Can't eval locals (NO_SOURCE_FILE:89)
Предположительно, проблема связана с разрешением символов, поэтому, возможно, мы попытаемся что-то решить с помощью синтаксической кавычки:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(str ~x)))
Теперь проблема в (foo (bar))
, поскольку это расширяет предложение else до (clojure.core/str (bar))
, которое выдает исключение, так как bar
не определено. Затем я попытался сделать некоторые махинации с eval
:
(defmacro foo [x]
`(if (and (coll? '~x) (= (first '~x) '~'bar))
:foobar
(eval '(str ~x))))
Но это не работает с let
привязками снова:
user=> (let [a 1 b (foo a)] b)
java.lang.Exception: Unable to resolve symbol: a in this context (NO_SOURCE_FILE:153)
Так что я действительно в растерянности. Кажется, что решение одной проблемы ломает другую. Есть ли лучший, более простой способ сделать этот макрос таким, чтобы он работал в следующих случаях:
- В
let
привязок
- С
(bar)
- С символами
P.S. Если кому-то интересно , почему Я хочу это сделать, я работаю над DSL для службы YQL Yahoo и хочу иметь возможность делать что-то вроде (select (table :t) ...)
, но мне нужно передавать в символах, а также литералах.