Как работает макрос Clojure Cond - PullRequest
       15

Как работает макрос Clojure Cond

0 голосов
/ 28 сентября 2018

Мне вполне комфортно с Clojure, но я всегда избегал макросов.В попытке исправить это, я читаю «Освоение макросов Clojure», а также смотрю на некоторые основные макросы Clojure в целом.

При чтении макроса cond меня немного запуталочто на самом деле оценивается, когда.Предполагая, что clauses не равен nil и что первоначальный тест when проходит, мы затем оцениваем вызов списка.List - это функция, поэтому она должна сначала оценить все свои аргументы, прежде чем вводить свое тело.Первый аргумент - это просто символ 'if, затем следующий аргумент - (first claues), который оценивает первый тест, но затем часть, которую я нашел немного запутанной, - это то, что происходит со следующим (третьим) аргументом.Выглядит как вся форма:

(if (next clauses)
    (second clauses)
    (throw (IllegalArgumentException.
            "cond requires an even number of forms")))


фактически вычисляется до того, как окончательное макроразложение возвращается для оценки.Если это правильно, означает ли это, что тест для четного числа форм выполняется до того, как макрос действительно развернут, и, следовательно, может вызывать исключение до того, как макрос фактически сгенерирует список для оценки во время выполнения?

(defmacro cond
  "Takes a set of test/expr pairs. It evaluates each test one at a
  time.  If a test returns logical true, cond evaluates and returns
  the value of the corresponding expr and doesn't evaluate any of the
  other tests or exprs. (cond) returns nil."
  {:added "1.0"}
  [& clauses]
    (when clauses
      (list 'if (first clauses)
            (if (next clauses)
                (second clauses)
                (throw (IllegalArgumentException.
                         "cond requires an even number of forms")))
            (cons 'clojure.core/cond (next (next clauses))))))

1 Ответ

0 голосов
/ 28 сентября 2018

Самый простой способ увидеть, как работает макрос, это проверить его с помощью clojure.core/macroexpand-1 или clojure.walk/macroexpand-all.

Например, мы можем увидеть, какследующая форма будет расширена:

(cond
  (pos? 1) :positive
  (neg? -1) :negative)

с macroexpand-1:

(macroexpand-1
  '(cond
    (pos? 1) :positive
    (neg? -1) :negative))

;; => (if (pos? 1) :positive (clojure.core/cond (neg? -1) :negative))

Мы можем видеть, что когда эта форма раскрывается, clauses привязывается к последовательности этих выражений: (pos? 1), :positive, (neg? -1) и :negative.

(first clauses) будут иметь значение (pos? 1), и его значение будет использоваться в качестве тестового выражения для испускаемого значения if.Затем макрос проверяет, есть ли у первого предиката требуемое выражение результата, проверяя, имеет ли оно более одного предложения: (next clauses) оценивается как (:positive (neg? -1) :negative), что верно, и истинная ветвь испускаемого if получит значение(second clauses), то есть :positive.

Другая ветвь испускаемого if получит (clojure.core/cond (neg? -1) :negative).Поскольку переданный код снова будет включать вызов макроса cond, он будет вызван снова и снова расширен.

Чтобы увидеть полностью развернутый код, мы можем использовать clojure.walk/macroexpand-all:

(require 'clojure.walk)

(clojure.walk/macroexpand-all
      '(cond
        (pos? 1) :positive
        (neg? -1) :negative))
;; => (if (pos? 1) :positive (if (neg? -1) :negative nil))

Чтобы расширить тему, если формы, включенные в clauses, оцениваются во время расширения макроса, мы можем добавить некоторые побочные эффекты в код:

(clojure.walk/macroexpand-all
  '(cond
     (do
       (println "(pos? 1) evaluated!")
       (pos? 1))
     (do
       (println ":positive evaluated1")
       :positive)

     (do
       (println "(neg? -1) evaluated!")
       (neg? -1))
     (do
       (println ":negative evaluated!")
       :negative)))
=>
(if
 (do (println "(pos? 1) evaluated!") (pos? 1))
 (do (println ":positive evaluated1") :positive)
 (if (do (println "(neg? -1) evaluated!") (neg? -1)) (do (println ":negative evaluated!") :negative) nil))

Как мы видим, побочные эффекты не выполнялись, потому чтони одно из предложений не было оценено во время расширения макроса.

Мы также можем проверить, оценивается ли вызов throw во время расширения макроса, предоставив clauses, что приведет к тому, что ветвь else в (if (next clauses) ... будетПозвонил:

(macroexpand-1 '(cond (pos? 1)))
java.lang.IllegalArgumentException: cond requires an even number of forms

Здесь мы видим, что было сгенерировано исключение, и расширение макроса макроса cond не завершилось нормально, возвращая код расширенного макроса.Причина, по которой форма throw вычисляется во время расширения макроса, заключается в том, что она не заключена в кавычки (например, `` (throw ...) `).

...