Во все той же незабвенной «On Lisp» Пола Грэма в главе 14 рассматривается такое понятие, как анафорический макрос. Анафора в естественном языке – термин, означающий ссылку на что-то в предшествующем разговоре. В языке программирования анафора – отсылка к предыдущим результатам вычислений. Например.
(defn my-long-calculation [param] param)
(aif (my-long-calculation 15)
(println result)
(println "The result is nil!")
Здесь aif – анафорический if, который результат вычисления функции my-long-calculation присвоит переменной result. Соответственно, result здесь – это анафора, которая ссылается на результат вычислений. Кроме того, ветка then здесь будет выполнена только в случае, когда result не равен nil, иначе управление перейдет ветке else.
Забавный, в общем макрос. Тем более, что, обратите внимание, пользователь переменную result не создает, это делает за пользователя макрос aif. Но в Clojure его нет, и нам предстоит его написать.
Проблема в том, что в Clojure захват лексической привязки result – очень неидиоматичное решение. Мне даже не удалось найти стандартного способа это сделать. Например.
(defmacro a [body]
`(let [b 15]
~@body))
(a (println b))
Здесь Clojure создаст исключение «Can't let qualified name: user/b». Можно, конечно, воспользоваться gensym:
(defmacro a [body]
(let [b (gensym "b")]
`(let [~b 15]
~@body)))
(a (println b))
Здесь ошибка уже «Unable to resolve symbol: b in this context», потому что gensym генерирует уникальное имя во избежание захвата переменной (variable capture).
Создадим свою версию gensym, результатом которой было бы предсказуемое имя переменной.
(defn make-sym [name] (. clojure.lang.Symbol (intern (str name))))
Теперь можно написать анафорический макрос aif.
(defmacro aif
([expr then]
(let [result (make-sym "result")]
`(let [~result ~expr]
(if (not (nil? ~result))
~then))))
([expr then else]
(let [result (make-sym "result")]
`(let [~result ~expr]
(if (not (nil? ~result))
~then
~else)))))
Одним словом, анафорические макросы в Clojure писать так же легко, как и любые другие.
Если передать в макрос имя требуемой переменной,
ОтветитьУдалитьто он (макрос) сокращается до неприлично малого.
(defmacro aif-2 [var expr then else]
`(if-let [~var ~expr] ~then ~else))
(aif-2 x (* 2 2)
(println x)
(println "NULL"))
Точно!
ОтветитьУдалитьНу а в моем варианте фишка в том, что имя переменной не передается, а подразумевается. Convention over configuration, так сказать :-)
;; -- why only lexical ?
ОтветитьУдалить;; (macroexpand-1 '(aif (+ 2 1) (* 2 it) 'nok))
;; (aif (+ 2 1) (* 2 it) 'nok)
;; (aif nil (* 2 it) 'nok)
(def it nil)
(defmacro aif [test body else]
`(binding [it ~test] (if it ~body ~else)) )
;; -- symbol is the function you want !
;; (macroexpand-1 '(aif2 (+ 2 1) (* 2 it) 'nok))
;; (aif2 (+ 2 1) (* 2 it) 'nok)
;; (aif2 nil (* 2 it) 'nok)
(defmacro aif2 [test body else]
(let [it (symbol "it")]
`(let [~it ~test] (if ~it ~body ~else)) ))
my 2 cents ...
Yep, that really works, thanks for simplifying the macro.
ОтветитьУдалитьThe only question is: how did you manage to read my blog? It's totally in russian and you (according to your profile) speak French. I'm totally sure that google translate makes a crappy translation from russian to English or French.
Actually I'm pleased that my post has attracted your attention. I'm also interested in your blog about clojure (http://clojadventure.blogspot.com/), trying to read it in translation to English.
Yes i'm using google translate to read your blog. It's not perfect but enough to understand main ideas :).
ОтветитьУдалить