Гигиенический макрос
Эта статья может быть слишком технической для понимания большинства читателей . ( Ноябрь 2016 г. ) |
В информатике гигиенические макросы — это макросы гарантированно не приведет к случайному захвату идентификаторов , расширение которых . Они являются особенностью таких языков программирования , как Scheme . [1] Дилан , [2] Раст , Ним и Джулия . Общая проблема случайного захвата была хорошо известна в сообществе Lisp до появления гигиенических макросов. Авторы макросов будут использовать функции языка, которые генерируют уникальные идентификаторы (например, gensym), или использовать запутанные идентификаторы, чтобы избежать проблемы. Гигиенические макросы — это программное решение проблемы захвата, интегрированное в расширитель макросов. Термин «гигиена» был придуман в статье Кольбекера и др. 1986 года, в которой было представлено гигиеническое макрорасширение, вдохновленное терминологией, используемой в математике. [3]
Проблема гигиены
[ редактировать ]Переменное затенение
[ редактировать ]В языках программирования, которые имеют негигиеничные системы макросов, существующие привязки переменных могут быть скрыты от макроса привязками переменных, которые создаются во время его расширения. В C эту проблему можно проиллюстрировать следующим фрагментом:
#define INCI(i) { int a=0; ++i; }
int main(void)
{
int a = 4, b = 8;
INCI(a);
INCI(b);
printf("a is now %d, b is now %d\n", a, b);
return 0;
}
Выполнение вышеизложенного через препроцессор C дает:
int main(void)
{
int a = 4, b = 8;
{ int a = 0; ++a; };
{ int a = 0; ++b; };
printf("a is now %d, b is now %d\n", a, b);
return 0;
}
Переменная a
объявленный в верхней области видимости затеняется , a
переменная в макросе, которая вводит новую область видимости . Как результат, a
никогда не изменяется при выполнении программы, как показывает вывод скомпилированной программы:
a is now 4, b is now 9
Переопределение функции стандартной библиотеки
[ редактировать ]Проблема гигиены может выходить за рамки привязки переменных. Рассмотрим этот макрос Common Lisp :
(defmacro my-unless (condition &body body)
`(if (not ,condition)
(progn
,@body)))
Хотя в этом макросе нет ссылок на переменные, он предполагает, что символы «if», «not» и «progn» привязаны к своим обычным определениям в стандартной библиотеке. Однако если приведенный выше макрос используется в следующем коде:
(flet ((not (x) x))
(my-unless t
(format t "This should not be printed!")))
Определение «не» было локально изменено, поэтому расширение my-unless
изменения.
Однако обратите внимание, что для Common Lisp такое поведение запрещено в соответствии с разделом 11.1.2.1.2 «Ограничения пакета COMMON-LISP для соответствующих программ» . В любом случае также возможно полностью переопределить функции. Некоторые реализации Common Lisp обеспечивают блокировку пакетов , чтобы предотвратить ошибочное изменение определений в пакетах пользователем.
Переопределение программно-определяемой функции
[ редактировать ]Конечно, проблема может возникнуть и для программно-определяемых функций аналогичным образом:
(defun user-defined-operator (cond)
(not cond))
(defmacro my-unless (condition &body body)
`(if (user-defined-operator ,condition)
(progn
,@body)))
; ... later ...
(flet ((user-defined-operator (x) x))
(my-unless t
(format t "This should not be printed!")))
Сайт использования переопределяет user-defined-operator
и, следовательно, меняет поведение макроса.
Стратегии, используемые в языках, в которых отсутствуют гигиенические макросы
[ редактировать ]Проблему гигиены можно решить с помощью обычных макросов, используя несколько альтернативных решений.
Обфускация
[ редактировать ]Самое простое решение, если во время расширения макроса требуется временное хранилище, — использовать в макросе необычные имена переменных в надежде, что те же имена никогда не будут использоваться остальной частью программы.
#define INCI(i) { int INCIa = 0; ++i; }
int main(void)
{
int a = 4, b = 8;
INCI(a);
INCI(b);
printf("a is now %d, b is now %d\n", a, b);
return 0;
}
Пока переменная с именем INCIa
создано, это решение дает правильный результат:
a is now 5, b is now 9
Проблема решена для текущей программы, но это решение не является надежным. Переменные, используемые внутри макроса, и переменные в остальной части программы должны синхронизироваться программистом. В частности, с помощью макроса INCI
по переменной INCIa
произойдет сбой так же, как исходный макрос не сработал для переменной a
.
Создание временного символа
[ редактировать ]В некоторых языках программирования можно создать новое имя переменной или символ и привязать его к временному местоположению. Система языковой обработки гарантирует, что оно никогда не будет конфликтовать с другим именем или местоположением в среде выполнения. Ответственность за использование этой функции в теле определения макроса остается за программистом. Этот метод использовался в MacLisp , где функция с именем gensym
может использоваться для создания нового имени символа. Подобные функции (обычно называемые gensym
также) существуют во многих Lisp-подобных языках, включая широко реализованный Common Lisp . стандарт [4] и Элисп .
Хотя создание символов решает проблему затенения переменных, оно не решает напрямую проблему переопределения функции. [5] Однако, gensym
, макросов и стандартных библиотечных функций достаточно для встраивания гигиеничных макросов в негигиеничный язык. [6]
Неинтернированный символ времени чтения
[ редактировать ]Это похоже на обфускацию, поскольку одно имя используется несколькими расширениями одного и того же макроса. Однако, в отличие от необычного имени, используется неинтернированный символ времени чтения (обозначается #:
нотации), для которых невозможно произойти вне макроса, аналогично gensym
.
Пакеты
[ редактировать ]При использовании таких пакетов, как Common Lisp, макрос просто использует закрытый символ из пакета, в котором определен макрос. Символ не будет случайно встречаться в коде пользователя. Пользовательский код должен будет проникнуть внутрь пакета, используя двойное двоеточие ( ::
) нотация, чтобы дать себе разрешение на использование частного символа, например cool-macros::secret-sym
. В этот момент вопрос случайного отсутствия гигиены становится спорным. Более того, стандарт ANSI Common Lisp классифицирует переопределение стандартных функций и операторов, глобально или локально, как вызов неопределенного поведения . Таким образом, реализация может диагностировать такое использование как ошибочное. Таким образом, система пакетов Lisp обеспечивает жизнеспособное и полное решение проблемы гигиены макросов, которую можно рассматривать как пример конфликта имен.
Например, в переопределения программно-определяемой функции примере my-unless
макрос может находиться в отдельном пакете, где user-defined-operator
является частным символом в этом пакете. Символ user-defined-operator
тогда в коде пользователя будет другой символ, не связанный с тем, который используется в определении my-unless
макрос.
Буквальные объекты
[ редактировать ]В некоторых языках расширение макроса не обязательно должно соответствовать текстовому коду; вместо того, чтобы расширяться до выражения, содержащего символ f
, макрос может создать расширение, содержащее фактический объект, на который ссылается f
. Аналогично, если макросу необходимо использовать локальные переменные или объекты, определенные в пакете макроса, он может расшириться до вызова замыкающего объекта, лексическое окружение которого соответствует определению макроса.
Гигиеническая трансформация
[ редактировать ]Гигиенические макросистемы на таких языках, как Scheme, используют процесс расширения макросов, который сохраняет лексическую область видимости всех идентификаторов и предотвращает случайный захват. Это свойство называется ссылочной прозрачностью . В случаях, когда желателен захват, некоторые системы позволяют программисту явно нарушать механизмы гигиены макросистемы.
Например, схема let-syntax
и define-syntax
Системы создания макросов гигиеничны, поэтому следующая схема реализации my-unless
будет иметь желаемое поведение:
(define-syntax my-unless
(syntax-rules ()
((_ condition body ...)
(if (not condition)
(begin body ...)))))
(let ((not (lambda (x) x)))
(my-unless #t
(display "This should not be printed!")
(newline)))
Гигиенический макропроцессор, отвечающий за преобразование шаблонов входной формы в выходную форму, обнаруживает конфликты символов и разрешает их путем временного изменения названий символов. Основная стратегия состоит в том, чтобы идентифицировать привязки в определении макроса и заменить эти имена на gensyms, а также идентифицировать свободные переменные в определении макроса и убедиться, что эти имена просматриваются в области определения макроса, а не в области, в которой был макрос. использовал.
Реализации
[ редактировать ]Макросистемы, которые автоматически обеспечивают соблюдение гигиены, созданы Scheme. Оригинальный алгоритм KFFD для гигиенической макросистемы был представлен Кольбекером в 1986 году. [3] В то время в реализациях Scheme не использовалась стандартная макросистема. Вскоре после этого, в 1987 году, Кольбекер и Ванд предложили декларативный язык на основе шаблонов для написания макросов, который был предшественником языка syntax-rules
макросредство, принятое стандартом R5RS. [1] [7] Синтаксические замыкания, альтернативный гигиенический механизм, были предложены Боуденом и Рисом в качестве альтернативы системе Кольбекера и др. в '88. [8] В отличие от алгоритма KFFD, синтаксические замыкания требуют от программиста явного указания разрешения области идентификатора. В 1993 году Дибвиг и др. представил syntax-case
макросистема, которая использует альтернативное представление синтаксиса и автоматически поддерживает гигиену. [9] syntax-case
система может выражать syntax-rules
язык шаблонов как производный макрос. Термин «макросистема» может быть неоднозначным, поскольку в контексте схемы он может относиться как к конструкции сопоставления с образцом (например, синтаксические правила), так и к структуре для представления и управления синтаксисом (например, синтаксический регистр, синтаксические замыкания). .
Синтаксис-правила
[ редактировать ]Синтаксические правила — это средство сопоставления шаблонов высокого уровня , которое пытается упростить написание макросов. Однако, syntax-rules
не способен кратко описать определенные классы макросов и недостаточен для выражения других макросистем. Правила синтаксиса описаны в документе R4RS в приложении, но не являются обязательными. Позже R5RS принял его в качестве стандартного средства макросов. Вот пример syntax-rules
макрос, меняющий местами значения двух переменных:
(define-syntax swap!
(syntax-rules ()
((_ a b)
(let ((temp a))
(set! a b)
(set! b temp)))))
Синтаксис-кейс
[ редактировать ]Из-за недостатков чисто syntax-rules
на основе макросистемы, стандарт схемы R6RS принял макросистему синтаксического регистра. [10] В отличие от syntax-rules
, syntax-case
содержит как язык сопоставления с образцом, так и низкоуровневые средства для написания макросов. Первый позволяет писать макросы декларативно, а второй позволяет реализовать альтернативные интерфейсы для написания макросов. Приведенный выше пример обмена практически идентичен syntax-case
потому что язык сопоставления с образцом аналогичен:
(define-syntax swap!
(lambda (stx)
(syntax-case stx ()
((_ a b)
(syntax
(let ((temp a))
(set! a b)
(set! b temp)))))))
Однако, syntax-case
более мощный, чем правила синтаксиса. Например, syntax-case
Макросы могут указывать дополнительные условия в своих правилах сопоставления с образцом с помощью произвольных функций Scheme. В качестве альтернативы автор макросов может отказаться от использования интерфейса сопоставления с образцом и напрямую манипулировать синтаксисом. Используя datum->syntax
макросы синтаксического регистра также могут намеренно захватывать идентификаторы, нарушая тем самым гигиену.
Другие системы
[ редактировать ]Для Scheme также были предложены и реализованы другие макросистемы. Синтаксические замыкания и явное переименование [11] две альтернативные макросистемы. Обе системы являются более низкими уровнями, чем правила синтаксиса, и оставляют соблюдение гигиены на усмотрение автора макросов. Это отличается как от синтаксических правил, так и от синтаксических регистров, которые по умолчанию автоматически обеспечивают соблюдение гигиены. Приведенные выше примеры замены показаны здесь с использованием синтаксического замыкания и явной реализации переименования соответственно:
;; syntactic closures
(define-syntax swap!
(sc-macro-transformer
(lambda (form environment)
(let ((a (close-syntax (cadr form) environment))
(b (close-syntax (caddr form) environment)))
`(let ((temp ,a))
(set! ,a ,b)
(set! ,b temp))))))
;; explicit renaming
(define-syntax swap!
(er-macro-transformer
(lambda (form rename compare)
(let ((a (cadr form))
(b (caddr form))
(temp (rename 'temp)))
`(,(rename 'let) ((,temp ,a))
(,(rename 'set!) ,a ,b)
(,(rename 'set!) ,b ,temp))))))
Языки с гигиеническими макросистемами
[ редактировать ]- Схема – синтаксические правила, синтаксический регистр, синтаксические замыкания и другие.
- Racket – вариант Scheme, его система макросов изначально была основана на синтаксическом регистре, но теперь имеет больше возможностей.
- Немерль [12]
- Дилан
- Эликсир [13]
- Nim
- Ржавчина
- Смешанный
- Mary2 - макротела с ограниченной областью действия на языке, производном от ALGOL 68 , около 1978 г.
- Юлия [14]
- Раку – поддерживает как гигиенические, так и антисанитарные макросы. [15]
Критика
[ редактировать ]Гигиеничные макросы обеспечивают безопасность и ссылочную прозрачность за счет того, что преднамеренный захват переменных становится менее простым. Дуг Хойт, автор книги Let Over Lambda , пишет: [16]
Почти все подходы, применяемые для уменьшения влияния захвата переменных, служат только для уменьшения того, что вы можете сделать с помощью defmacro. Гигиенические макросы в лучших ситуациях являются ограждением безопасности для новичка; в худших ситуациях они образуют электрический забор, запирая своих жертв в продезинфицированной и безопасной тюрьме.
— Дуг Хойт
Многие гигиенические макросистемы предлагают аварийные люки без ущерба для гарантий, обеспечиваемых гигиеной; например, Racket позволяет вам определять параметры синтаксиса , которые позволяют выборочно вводить связанные переменные. Грегг Хендершотт приводит пример в книге «Страх перед макросами». [17] реализации анафорического оператора if таким способом.
См. также
[ редактировать ]Примечания
[ редактировать ]- ^ Jump up to: а б Келси, Ричард; Клингер, Уильям; Рис, Джонатан; и др. (август 1998 г.). «Пересмотренный 5 Отчет об алгоритмической языковой схеме» . Высшие порядки и символические вычисления . 11 (1): 7–105. doi : 10.1023/A:1010051815785 .
- ^ Фейнберг, Н.; Кин, ЮВ; Мэтьюз, РОД; Withington, PT (1997), Программирование Дилана: объектно-ориентированный и динамический язык , Addison Wesley Longman Publishing Co., Inc.
- ^ Jump up to: а б Кольбекер, Э.; Фридман, ДП; Феллейзен, М.; Дуба, Б. (1986). «Гигиеническое макрорасширение» (PDF) . Конференция ACM по LISP и функциональному программированию .
- ^ «CLHS: Функция GENSYM» .
- ^ «гигиена против генсима» . Community.schemewiki.org . Проверено 11 июня 2022 г.
- ^ Костанца, Паскаль; Д'Ондт, Тео (2010). «Внедрение гигиенически совместимых макросов в негигиеническую макросистему» . Журнал универсальной информатики . 16 (2): 271–295. CiteSeerX 10.1.1.424.5218 . дои : 10.3217/jucs-016-02-0271 .
- ^ Кольбекер, Э.; Ванд, М. (1987). «Макрос на примере: получение синтаксических преобразований на основе их спецификаций» (PDF) . Симпозиум по основам языков программирования .
- ^ Боуден, А.; Рис, Дж. (1988). «Синтаксические замыкания» (PDF) . Лисп и функциональное программирование . Архивировано (PDF) из оригинала 3 сентября 2019 г.
- ^ Дыбвиг, К; Хиеб, Р; Бругерман, К. (1993). «Синтаксическая абстракция в схеме» (PDF) . LISP и символьные вычисления . 5 (4): 295–326. дои : 10.1007/BF01806308 . S2CID 15737919 .
- ^ Спербер, Майкл; Дибвиг, Р. Кент; Флэтт, Мэтью; Ван Страатен, Антон; и др. (август 2007 г.). «Пересмотренный 6 Отчет о схеме алгоритмического языка (R6RS)» . Руководящий комитет схемы . Проверено 13 сентября 2011 г.
- ^ Клингер, Уилл (1991). «Гигиеничные макросы за счет явного переименования». Указатели Lisp ACM SIGPLAN . 4 (4): 25–28. дои : 10.1145/1317265.1317269 . S2CID 14628409 .
- ^ Skalski, K.; Moskal, M; Olszta, P, Metaprogramming in Nemerle (PDF) , archived from the original (PDF) on 2012-11-13
- ^ «Макросы» .
- ^ «Метапрограммирование: язык Джулии» . Архивировано из оригинала 4 мая 2013 г. Проверено 03 марта 2014 г.
- ^ «Краткий обзор 6: Подпрограммы» . Архивировано из оригинала 6 января 2014 г. Проверено 3 июня 2014 г.
- ^ [1] , Let Over Lambda — 50 лет Lisp Дуг Хойт
- ^ [2] , Боязнь макросов
Ссылки
[ редактировать ]Эта статья включает список общих ссылок , но в ней отсутствуют достаточные соответствующие встроенные цитаты . ( Апрель 2012 г. ) |
- О Лиспе , Пол Грэм
- синтаксические правила в Schemawiki
- синтаксический регистр в Schemawiki
- примеры синтаксического регистра на Schemawiki
- синтаксические замыкания в схемавики
- более простые макросы на схемавики
- примеры простых макросов на Schemawiki
- Написание гигиенических макросов в Scheme с использованием Syntax-Case