Итак... Я новичок в схемах r6rs и изучаю макросы. Кто-нибудь может объяснить мне, что подразумевается под «гигиеной»?
Заранее спасибо.
Итак... Я новичок в схемах r6rs и изучаю макросы. Кто-нибудь может объяснить мне, что подразумевается под «гигиеной»?
Заранее спасибо.
Гигиена часто используется в контексте макросов. Гигиенический макрос не использует имена переменных, которые могут мешать расширяемому коду. Вот пример. Допустим, мы хотим определить специальную форму or с помощью макроса. Интуитивно,
(or a b c ... d) расширится до чего-то вроде (let ((tmp a)) (if tmp a (or b c ... d))). (Я опускаю пустой случай (or) для простоты.)
Теперь, если бы имя tmp действительно было добавлено в код, как в приведенном выше наброске расширения, это было бы негигиенично и плохо, потому что могло бы помешать другой переменной с тем же именем. Скажем, мы хотели оценить
(let ((tmp 1)) (or #f tmp))
Используя наше интуитивное расширение, это станет
(let ((tmp 1)) (let ((tmp #f)) (if tmp (or tmp)))
tmp из макроса затеняет самый внешний tmp, поэтому результат будет #f вместо 1.
Теперь, если бы макрос был гигиеничным (а в Схеме это автоматически происходит при использовании syntax-rules), то вместо использования имени tmp для расширения вы бы использовали символ, который гарантированно больше нигде в коде не появится. Вы можете использовать gensym в Common Lisp.
Пол Грэм о Лиспе содержит дополнительные материалы по макросам.
Если вы представляете, что макрос просто расширяется до того места, где он используется, то вы также можете представить, что если вы используете переменную a в своем макросе, может быть уже существовать переменная a, определенная в место, где используется этот макрос.
Это не то a, которое вам нужно!
Макросистема, в которой подобное произойти не может, называется гигиеничной.
Есть несколько способов справиться с этой проблемой. Один из способов — просто использовать очень длинные, очень загадочные, очень непредсказуемые имена переменных в ваших макросах.
Чуть более усовершенствованная версия этого подхода — gensym, используемый некоторыми другими макросистемами: вместо вас программист придумывает очень длинное, очень загадочное и очень непредсказуемое имя переменной, вы можете вызвать gensym, которая генерирует для вас очень длинное, очень загадочное, очень непредсказуемое и уникальное имя переменной для вас.
И, как я уже сказал, в гигиеничной макросистеме такие коллизии вообще не могут происходить. Как сделать макросистему гигиеничной — это сам по себе интересный вопрос, и сообщество Scheme потратило на этот вопрос несколько десятилетий, и они продолжают придумывать все лучшие и лучшие способы сделать это.
a, которые вам нужны.
- person Thanatos; 10.06.2010
(let ((if "bleh")) (my-macro)). Это не может быть решено только с помощью gensyms.
- person Eli Barzilay; 10.06.2010
Я так рад узнать, что этот язык все еще используется! Гигиенический код — это код, который при внедрении (через макрос) не вызывает конфликтов с существующими переменными.
В Википедии есть много полезной информации об этом: http://en.wikipedia.org/wiki/Hygienic_macro
Вот что я нашел. Объяснить, что это значит, совсем другое дело!
http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-1.html#node_toc_node_sec_12.1
Макросы преобразуют код: они берут один бит кода и преобразуют его во что-то другое. В рамках этого преобразования они могут окружить этот код дополнительным кодом. Если исходный код ссылается на переменную a, а добавленный к ней код определяет новую версию a, то исходный код не будет работать должным образом, поскольку он будет обращаться к неправильному a: если
(myfunc a)
исходный код, который ожидает, что a будет целым числом, а макрос берет X и преобразует его в
(let ((a nil)) X)
Тогда макрос будет работать нормально для
(myfunc b)
но (myfunc a) превратится в
(let ((a nil)) (myfunc a))
что не сработает, потому что myfunc будет применено к nil, а не к ожидаемому целому числу.
Гигиенический макрос позволяет избежать этой проблемы доступа к неправильной переменной (и аналогичной проблемы наоборот), гарантируя, что используемые имена уникальны.
В Википедии есть хорошее объяснение гигиенических макросов.
Помимо всего перечисленного, в гигиенических макросах Схемы есть еще один важный момент, вытекающий из лексического объема.
Скажем, у нас есть:
(syntax-rules () ((_ a b) (+ a b)))
Как часть макроса, он обязательно вставит +, он также вставит его, когда + уже есть, но затем другой символ, который имеет то же значение, что и +. Он связывает символы со значением, которое они имели в лексическом окружении, в котором находится syntax-rules, а не там, где оно применяется, в конце концов, мы ограничены лексически. Скорее всего, он вставит туда совершенно новый символ, но тот, который глобально привязан к тому же значению, что и + в месте определения макроса. Это наиболее удобно, когда мы используем такую конструкцию, как:
(let ((+ *))
; piece of code that is transformed
)
Таким образом, автору или пользователю макроса не нужно заботиться о том, чтобы его использование прошло успешно.
(let ((if +)) ...), например, не потерпел крах, пока не понял, что это было из-за лексической области видимости.
- person Zorf; 11.06.2010