Гигиенический макрос
![]() | Эта статья может быть слишком технической для понимания большинства читателей . ( Ноябрь 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 таким способом.
См. также [ править ]
Примечания [ править ]
- ↑ Перейти обратно: Перейти обратно: а б Келси, Ричард; Клингер, Уильям; Рис, Джонатан; и др. (август 1998 г.). «Пересмотренный 5 Отчет об алгоритмической языковой схеме» . Высшие порядки и символические вычисления . 11 (1): 7–105. doi : 10.1023/A:1010051815785 .
- ^ Фейнберг, Н.; Кин, ЮВ; Мэтьюз, РОД; Withington, PT (1997), Программирование Дилана: объектно-ориентированный и динамический язык , Addison Wesley Longman Publishing Co., Inc.
- ↑ Перейти обратно: Перейти обратно: а б Кольбекер, Э.; Фридман, ДП; Феллейзен, М.; Дуба, Б. (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