Logo
Книга для начинающих
ВходРегистрация
/
Программирование
/
Курс Clojure
/

Gensym

Clojure: Gensym

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

(defmacro return-some-list [x]
  (list 'let ['one 5]
  ['one x]))

(return-some-list 1)
[5 1]

Пока что все в порядке, но теперь добавим переменную one в наше пространство имен:

(def one 1)

(return-some-list one)
[5 5]

Совсем не то, что ожидалось, не правда ли? Помните, что аргументы переданные в макрос не вычисляются и из-за того, что мы возвращаем символ one в контексте вызова формы let, значение one перетирается 5.

(macroexpand-1 '(return-some-list one))
(let [one 5] [one one]) ; one здесь равняется 5

Этот эффект называется symbol capture или variable capture (захват/перекрытие символа или переменной, если переводить на русский), одна из частых ошибок, возникающих при создании макросов.

Можно попытаться процитировать объявление внутри макроса, однако это вызовет исключение. Попробуем:

(defmacro return-some-list [x]
  `(let [one 5]
    [one ~x]))
#'user/return-some-list

(return-some-list one)
Syntax error macroexpanding clojure.core/let .
user/one - failed: simple-symbol? at: [:bindings :form :local-symbol] spec: :clojure.core.specs.alpha/local-name

Это происходит из-за того, что let принимает только простые символы, в то время как сложные символы - нет (на англ. fully-qualified symbols). Так как источник ошибки уходит глубоко во внутренности Clojure и потребует немало времени и сил, чтобы понять, что вообще происходит, создатели языка позаботились об этом понятным исключением failed: simple-symbol?.

(simple-symbol? 'one)
true

(simple-symbol? 'user/one) ; в этом символе участвует еще и пространство имен
false

Однако, чтобы решить проблему с перекрытием объявления one, мы бы могли использовать какое-нибудь уникальное название и Clojure представляет такую возможность благодаря gensym!

(gensym)
G__2144 ; у вас скорее всего вывод будет отличаться

Проблема с перекрытием объявлений достаточно распространенная, поэтому в Clojure есть синтаксический сахар под это дело, называется он autogensym или #:

`(one#)
(one__2148__auto__)

(simple-symbol? `one#)
true

А теперь воспользуемся полученными знаниями для нашего макроса, в котором происходило перекрытие one:

(defmacro return-some-list [x]
  `(let [one# 5]
      [one# ~x]))

(return-some-list one)
[5 1] ; работает!

Вот и все, это почти все инструменты, которые используются при написании макросов, однако нужно обсудить еще несколько важных вещей в следующем упражнении.

Задание

Создайте макрос auto-sum, внутри которого объявляется символ my-var со значением 10 и складывается с переданным в макрос числом (не забывайте про перекрытие объявлений!).

Полезное

  • Официальная документация

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

Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":

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

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

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

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

Gensym

Clojure: Gensym

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

(defmacro return-some-list [x]
  (list 'let ['one 5]
  ['one x]))

(return-some-list 1)
[5 1]

Пока что все в порядке, но теперь добавим переменную one в наше пространство имен:

(def one 1)

(return-some-list one)
[5 5]

Совсем не то, что ожидалось, не правда ли? Помните, что аргументы переданные в макрос не вычисляются и из-за того, что мы возвращаем символ one в контексте вызова формы let, значение one перетирается 5.

(macroexpand-1 '(return-some-list one))
(let [one 5] [one one]) ; one здесь равняется 5

Этот эффект называется symbol capture или variable capture (захват/перекрытие символа или переменной, если переводить на русский), одна из частых ошибок, возникающих при создании макросов.

Можно попытаться процитировать объявление внутри макроса, однако это вызовет исключение. Попробуем:

(defmacro return-some-list [x]
  `(let [one 5]
    [one ~x]))
#'user/return-some-list

(return-some-list one)
Syntax error macroexpanding clojure.core/let .
user/one - failed: simple-symbol? at: [:bindings :form :local-symbol] spec: :clojure.core.specs.alpha/local-name

Это происходит из-за того, что let принимает только простые символы, в то время как сложные символы - нет (на англ. fully-qualified symbols). Так как источник ошибки уходит глубоко во внутренности Clojure и потребует немало времени и сил, чтобы понять, что вообще происходит, создатели языка позаботились об этом понятным исключением failed: simple-symbol?.

(simple-symbol? 'one)
true

(simple-symbol? 'user/one) ; в этом символе участвует еще и пространство имен
false

Однако, чтобы решить проблему с перекрытием объявления one, мы бы могли использовать какое-нибудь уникальное название и Clojure представляет такую возможность благодаря gensym!

(gensym)
G__2144 ; у вас скорее всего вывод будет отличаться

Проблема с перекрытием объявлений достаточно распространенная, поэтому в Clojure есть синтаксический сахар под это дело, называется он autogensym или #:

`(one#)
(one__2148__auto__)

(simple-symbol? `one#)
true

А теперь воспользуемся полученными знаниями для нашего макроса, в котором происходило перекрытие one:

(defmacro return-some-list [x]
  `(let [one# 5]
      [one# ~x]))

(return-some-list one)
[5 1] ; работает!

Вот и все, это почти все инструменты, которые используются при написании макросов, однако нужно обсудить еще несколько важных вещей в следующем упражнении.

Задание

Создайте макрос auto-sum, внутри которого объявляется символ my-var со значением 10 и складывается с переданным в макрос числом (не забывайте про перекрытие объявлений!).

Полезное

  • Официальная документация

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

Если вы зашли в тупик, то самое время поговорить с нашим асситентом Тота во вкладке "ИИ-помощник":

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

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

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

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

Ваше упражнение проверяется по этим тестам

(ns gensym-test
  (:require [test-helper :refer [assert-solution]]
            [index :refer [auto-sum]]))

(assert-solution [[2] [12] [112]] [12 22 122] (fn [arg] (do (intern 'index 'my-var 0) (auto-sum arg))))
← ПредыдущийСледующий →

Решение учителя откроется через:

20:00

waiting_clock
← ПредыдущийСледующий →