Реализация пока как макрос - PullRequest
0 голосов
/ 14 мая 2018

Вот моя неудачная попытка:

(defmacro until
  [condition body setup increment]
  `(let [c ~@condition]
    (loop [i setup]
      (when (not c)
        (do
          ~@body
          (recur ~@increment))))))

(def i 1)

(until (> i 5)
  (println "Number " i)
  0
  (inc i))

Я получаю: CompilerException java.lang.RuntimeException: не могу разрешить квалифицированное имя: clojure-noob.core / c

Я ожидаюэтот вывод: Number 1 Number 2 Number 3 Number 4 Number 5

Что не так?

Ответы [ 2 ]

0 голосов
/ 14 мая 2018

Есть несколько проблем с макросом:

  • Вам нужно генерировать символы для привязок внутри макросов. Удобный способ сделать это - добавить к именам суффикс #. В противном случае привязки в ваших макросах могут затмевать привязки в других местах вашего кода.
  • Некоторые из входов макроса были излишне сращены в кавычках, т.е. ~@ вместо ~

Вот версия макроса, который будет компилироваться / расширяться:

(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))
0 голосов
/ 14 мая 2018

Также измените строку let:

... 
`(let [c# ~@condition]
... 

Затем переименуйте все ссылки с c на c#.Постфикс # генерирует уникальный идентификатор без определения пространства имен, чтобы гарантировать, что символ, созданный макросом, не конфликтует с какими-либо существующими символами в контексте, в который расширяется макрос.Всякий раз, когда вы связываете символ в форме в кавычках, вы должны использовать # для предотвращения столкновений, если только у вас нет веской причины не использовать его.

Почему это необходимо в этом случае?Я не могу точно вспомнить причину, но если я правильно помню, любые символы, связанные в форме синтаксиса в кавычках (`()), имеют квалификацию пространства имен, и вы не можете использовать let для создания символов с квалификацией пространства имен.

Вы можете воссоздать ошибку, набрав:

(let [a/a 1]
  a/a)
...