Есть несколько проблем с макросом:
- Вам нужно генерировать символы для привязок внутри макросов. Удобный способ сделать это - добавить к именам суффикс
#
. В противном случае привязки в ваших макросах могут затмевать привязки в других местах вашего кода.
- Некоторые из входов макроса были излишне сращены в кавычках, т.е.
~@
вместо ~
Вот версия макроса, который будет компилироваться / расширяться:
(defmacro until [condition body setup increment]
`(let [c# ~condition]
(loop [i# ~setup]
(when-not c#
~body
(recur ~increment)))))
Но в вашем примере это зациклится вечно, потому что condition
оценивается только один раз, и значение i
никогда не изменится. Мы могли бы это исправить:
(defmacro until [condition body increment]
`(loop []
(when-not ~condition
~body
~increment
(recur))))
И нам нужно сделать i
изменяемым, если мы хотим изменить его значение:
(def i (atom 1))
(until (> @i 5)
(println "Number " @i)
(swap! i inc))
;; Number 1
;; Number 2
;; Number 3
;; Number 4
;; Number 5
Но теперь until
начинает выглядеть как дополнение к while
, и его дополнительная сложность не кажется полезной.
(defmacro until [test & body]
`(loop []
(when-not ~test
~@body
(recur))))
Эта версия until
идентична while
, за исключением того, что тест инвертирован, а приведенный выше пример кода с атомом по-прежнему работает правильно. Мы можем еще больше упростить until
, используя while
напрямую, и в конечном итоге он расширится до того же кода:
(defmacro until [test & body]
`(while (not ~test) ~@body))