Теперь посмотрим, как выполняется код внутри макросов:
(defmacro id-mac [x]
"Hello, macro!"
x)
(macroexpand-1 '(id-mac (println "str")))
; => (println "str")
Как видно из примера, строка, "Hello, macro!"
нигде не появилась, следовательно, как и в обычной функции, возвращается последняя форма. Вспомним макрос из прошлого упражнения:
(defmacro identity-macro [x]
(println "identity of x")
x)
Как мы уже выяснили, когда мы вызываем identity-macro
, форма (println "identity of x")
вызывается до возвращения последней формы, что еще раз подтверждает, что тело макроса выполняется так же как и в обычной функции. Это будет вторым правилом макросов: Тело макросов выполняется в соответствии с обычными правилами Clojure.
Посмотрим еще несколько функций и макросов:
(defn triplet-fn [a b c]
(list a b c))
(defmacro triplet-macro [a b c]
(list a b c))
(triplet-fn 1 2 3)
; => (1 2 3)
(triplet-macro 1 2 3)
; => java.lang.Exception: Cannot call 1 as a function.
; Что-то пошло не так, посмотрим, во что макрос разворачивается
(macroexpand '(triplet-macro 1 2 3))
; => (1 2 3)
; Хм, кажется все начинает сходиться, попробуем вызвать макрос напрямую
(eval (macroexpand '(triplet-macro 1 2 3)))
; => java.lang.Exception: Cannot call 1 as a function.
; Ошибка такая же, как и в примере выше, Clojure пытается
; выполнить код (1 2 3), который не является валидной формой
Пора подводить итоги! Из примера выше можно сформировать еще одно, третье правило макросов: Данные, возвращаемые макросом немедленно вычисляются и результат этого вычисления отдается наружу при вызове этого макроса (третье правило не совсем корректно, но для нынешнего понимания этого пока что достаточно).
Полезно помнить, что возвращаемая форма выполняется дважды, когда вы пишете макрос и единожды, когда пишете обычную функцию. Теперь вспомним еще раз все правила, которые сформулировали:
Исправьте triplet-macro
, чтобы он работал так же, как и triplet-fn
(не забывайте третье правило макросов!).
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1(ns macro-rules-test
2 (:require [test-helper :refer [assert-solution]]
3 [index :refer [triplet-macro]]))
4
5(assert-solution
6 [[[1 2 3]] [[-1 -2 -3]] [[1.2 0 2/3]] [["a" "b" ["c"]]]]
7 [(list 1 2 3) (list -1 -2 -3) (list 1.2 0 2/3) (list "a" "b" ["c"])]
8 (fn [[a b c]] (triplet-macro a b c)))
9
Решение учителя откроется через: