Бесплатный курс по Clojure. Зарегистрируйтесь для отслеживания прогресса →

Clojure: Splicing

Попробуем решить еще одну странную задачку! Создадим макрос, который принимает вектор чисел, выводит их на экран и затем возвращает вектор, попробуйте сделать такой макрос сами, перед тем как читать дальше.

А теперь рассмотрим макрос, который решает нашу задачу:

(defmacro print-els [coll]
  `(do ~(map println coll)
    ~coll))

(print-els [1 2 3])
1
2
3
Syntax error (IllegalArgumentException)
Can't call nil, form: (nil nil nil)

Хммм, не работает, воспользуемся macroexpand-1:

(macroexpand-1 '(print-els [1 2 3]))
1
2
3
(do (nil nil nil) [1 2 3])

Итак, вызов ~(map println coll) вернул (nil nil nil), а так как nil нельзя вызвать, возникает ошибка. Однако, эта часть кода работает!

(do (map println [1 2 3]))
1
2
3
(nil nil nil)

; и этот код работает
(do nil nil nil [1 2 3])
[1 2 3]

По сути, мы хотим чтобы макрос и возвращал код выше.

Для решения этой проблемы воспользуемся новым оператором ~@ (unquote splicing). Этот оператор используется, если в макросе нужно получить содержание Clojure формы.

(defmacro print-els [coll]
  `(do ~@(map println coll)
    ~coll))

(print-els [1 2 3])
1
2
3
[1 2 3]

(macroexpand-1 '(print-els [1 2 3]))
1
2
3
(do nil nil nil [1 2 3])

А может мы просто вернем форму и вычислим ее снаружи макроса? Попробуем так:

(defmacro print-els* [coll]
  `(do (map println ~coll)
    ~coll))

Но что произойдет, если в пространстве имен, в котором мы определяем наш макрос, форма map означает нечто иное? Допустим, map будет определять хеш-мап?

; переопределим форму map
(def map #{:a 1})

; числа 1 2 3 не вывелись в консоль

(print-els* [1 2 3])
[1 2 3]

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

Но как мы видим, контекст, в котором мы вызываем макрос, тоже важен! Поэтому немного дополним правило: Данные, возвращаемые макросом, немедленно вычисляются в контексте пространства имен, в котором макрос был вызван.

Задание

Создайте макрос strange-print, который принимает строку, выводит в консоль строку сначала в обратном порядке, затем полностью в верхнем регистре, а затем полностью в нижнем и верните из макроса исходную строку.

А потом вызовите созданный макрос для строк: foo, !baz!, cloJURE.

Пример:

(strange-print "foo")
oof
FOO
foo
"foo"

(strange-print "!baz!")
!zab!
!BAZ!
!baz!
"!baz!"
Упражнение не проходит проверку — что делать? 😶

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨

Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.

Мой код отличается от решения учителя 🤔

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно 🙄

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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

Полезное


Нашли ошибку? Есть что добавить? Пулреквесты приветствуются https://github.com/hexlet-basics
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе