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!"

Полезное

Команда проекта находится в телеграм-сообществе. Там можно задать любой вопрос и повлиять на проект

Нашли ошибку? Есть что добавить? Пулреквесты приветствуются