Гигиенический макрос
![]() | Эта статья может быть слишком технической для понимания большинства читателей . ( Ноябрь 2016 г. ) |
В информатике , гигиенические макросы — это макросы гарантированно не приведет к случайному захвату идентификаторов расширение которых . Они являются особенностью таких языков программирования , как Scheme . [1] Дилан , [2] Раст , Ним и Джулия . Общая проблема случайного захвата была хорошо известна в сообществе Lisp до появления гигиенических макросов. Авторы макросов будут использовать функции языка, которые генерируют уникальные идентификаторы (например, gensym) или использовать запутанные идентификаторы, чтобы избежать проблемы. Гигиенические макросы — это программное решение проблемы захвата, интегрированное в расширитель макросов. Термин «гигиена» был придуман в статье Кольбекера и др. 1986 года, в которой было введено гигиеническое макрорасширение, вдохновленное терминологией, используемой в математике. [3]
Проблема гигиены [ править ]
Переменное затенение [ править ]
В языках программирования, которые имеют негигиеничные системы макросов, существующие привязки переменных могут быть скрыты от макроса привязками переменных, которые создаются во время его расширения. В C эту проблему можно проиллюстрировать следующим фрагментом:
#define INCI(i) { int a=0; ++я; }
Int main ( void )
{
int a = 4 , b = 8 ;
ИНЦИ ( а );
ИНЦИ ( б );
printf ( "a теперь %d, b теперь %d \n " , a , b );
вернуть 0 ;
}
Выполнение вышеизложенного через препроцессор C дает:
int main ( void )
{
int a = 4 , b = 8 ;
{ интервал а = 0 ; ++ а ; };
{ интервал а = 0 ; ++ б ; };
printf ( "a теперь %d, b теперь %d \n " , a , b );
вернуть 0 ;
}
Переменная a
объявленный в верхней области видимости затеняется , a
переменная в макросе, которая вводит новую область видимости . Как результат, a
никогда не изменяется при выполнении программы, как показывает вывод скомпилированной программы:
А сейчас 4, Б сейчас 9
Переопределение функции стандартной библиотеки [ править ]
Проблема гигиены может выходить за рамки привязки переменных. Рассмотрим этот Common Lisp макрос :
( defmacro my-unless ( условие &body тело )
` ( если ( не , условие )
( progn
,@ тело )))
Хотя в этом макросе нет ссылок на переменные, он предполагает, что символы «if», «not» и «progn» привязаны к своим обычным определениям в стандартной библиотеке. Однако если приведенный выше макрос используется в следующем коде:
( flet (( not ( x ) x ))
( my-unless t
( format t "Это не следует печатать!" )))
Определение «не» было локально изменено, поэтому расширение my-unless
изменения.
Однако обратите внимание, что для Common Lisp такое поведение запрещено в соответствии с разделом 11.1.2.1.2 «Ограничения пакета COMMON-LISP для соответствующих программ» . В любом случае также возможно полностью переопределить функции. Некоторые реализации Common Lisp обеспечивают блокировку пакетов , чтобы предотвратить ошибочное изменение определений в пакетах пользователем.
Переопределение программно-определяемой функции [ править ]
Конечно, проблема может возникнуть и для программно-определяемых функций аналогичным образом:
( defun определяемый пользователем оператор ( cond )
( not cond ))
( defmacro my-unless ( условие &body body )
` ( if ( определяемый пользователем оператор , условие )
( progn
,@ body )))
; ... позже ...
( flet (( определяемый пользователем оператор ( x ) x ))
( my-unless t
( format t "Это не следует печатать!" )))
Сайт использования переопределяет user-defined-operator
и, следовательно, меняет поведение макроса.
используемые в языках, в которых отсутствуют макросы Стратегии , гигиенические
Проблему гигиены можно решить с помощью обычных макросов, используя несколько альтернативных решений.
Обфускация [ править ]
Самое простое решение, если во время раскрытия макроса требуется временное хранилище, — использовать в макросе необычные имена переменных в надежде, что те же имена никогда не будут использоваться остальной частью программы.
#define INCI(i) { int INCIa = 0; ++я; }
Int main ( void )
{
int a = 4 , b = 8 ;
ИНЦИ ( а );
ИНЦИ ( б );
printf ( "a теперь %d, b теперь %d \n " , a , b );
вернуть 0 ;
}
Пока переменная с именем INCIa
создано, это решение дает правильный результат:
А сейчас 5, Б сейчас 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
( синтаксические правила ()
(( _ условия тело ... )
( if ( не условие )
( начало тела ... )))))
( let (( not ( лямбда ( x ) x )))
( my-unless #t
( отобразить «Это не следует печатать!» )
( новая строка )))
Гигиенический макропроцессор, отвечающий за преобразование шаблонов входной формы в выходную форму, обнаруживает конфликты символов и разрешает их путем временного изменения названий символов. Основная стратегия состоит в том, чтобы идентифицировать привязки в определении макроса и заменить эти имена на 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
макрос, меняющий местами значения двух переменных:
( замена синтаксиса определения !
( правила синтаксиса ()
(( _ a b )
( let (( temp a ))
( set! a b )
( set! b temp )))))
Синтаксис-кейс [ править ]
Из-за недостатков чисто syntax-rules
на основе макросистемы, стандарт схемы R6RS принял макросистему синтаксического регистра. [10] В отличие от syntax-rules
, syntax-case
содержит как язык сопоставления с образцом, так и низкоуровневые средства для написания макросов. Первый позволяет писать макросы декларативно, а второй позволяет реализовать альтернативные интерфейсы для написания макросов. Приведенный выше пример обмена практически идентичен syntax-case
потому что язык сопоставления с образцом аналогичен:
( замена синтаксиса определения !
( лямбда ( stx )
( синтаксис-case stx ()
(( _ a b )
( синтаксис
( let (( temp a ))
( set! a b )
( set! b temp ))))) ))
Однако, syntax-case
более мощный, чем правила синтаксиса. Например, syntax-case
Макросы могут указывать дополнительные условия в своих правилах сопоставления с образцом с помощью произвольных функций Scheme. В качестве альтернативы автор макросов может отказаться от использования интерфейса сопоставления с образцом и напрямую манипулировать синтаксисом. Используя datum->syntax
макросы синтаксического регистра также могут намеренно захватывать идентификаторы, нарушая тем самым гигиену.
Другие системы [ править ]
Для Scheme также были предложены и реализованы другие макросистемы. Синтаксические замыкания и явное переименование [11] две альтернативные макросистемы. Обе системы являются более низкими уровнями, чем правила синтаксиса, и оставляют соблюдение гигиены на усмотрение автора макросов. Это отличается как от синтаксических правил, так и от синтаксических регистров, которые по умолчанию автоматически обеспечивают соблюдение гигиены. Приведенные выше примеры замены показаны здесь с использованием синтаксического замыкания и явной реализации переименования соответственно:
;; синтаксические замыкания
( define-syntax swap!
( sc-macro-transformer
( лямбда ( формы среда )
( let (( a ( close-syntax ( cadr form ) среда )
( b ( среда close-syntax ( cadr form ) ) ) ))
` ( let (( temp , a ))
( set! , a , b )
( set! , b temp ))))))
;; явное переименование
( define-syntax swap!
( er-macro-transformer
( лямбда ( form rename Compare )
( let (( a ( cadr form ))
( b ( cadr form ))
( temp ( rename 'temp )))
` ( , ( переименовать 'let ) (( , temp , a ))
( , ( переименовать ' набор! ) , a , b )
( , ( переименовать ' набор! ) , 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) . Симпозиум по основам языков программирования .
- ^ Bawden, A.; Rees, J. (1988). "Syntactic closures" (PDF). Lisp and Functional Programming. Archived (PDF) from the original on September 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 .
- ^ Скальски, К.; Москаль, М; Ольшта, П. Метапрограммирование в Nemerle (PDF) , заархивировано из оригинала (PDF) 13 ноября 2012 г.
- ^ «Макросы» .
- ^ «Метапрограммирование: язык Джулии» . Архивировано из оригинала 4 мая 2013 г. Проверено 03 марта 2014 г.
- ^ «Краткий обзор 6: Подпрограммы» . Архивировано из оригинала 6 января 2014 г. Проверено 3 июня 2014 г.
- ^ [1] , Let Over Lambda — 50 лет Lisp Дуг Хойт
- ^ [2] , Боязнь макросов
Ссылки [ править ]
![]() | Эта статья включает список общих ссылок , но в ней отсутствуют достаточные соответствующие встроенные цитаты . ( Апрель 2012 г. ) |
- О Лиспе , Пол Грэм
- синтаксические правила на Schemawiki
- синтаксический регистр в Schemawiki
- примеры синтаксического регистра на Schemawiki
- синтаксические замыкания в Schemawiki
- более простые макросы на схемавики
- примеры простых макросов на Schemawiki
- Написание гигиенических макросов в Scheme с использованием Syntax-Case