Jump to content

Гигиенический макрос

(Перенаправлено из Гигиенических макросов )

В информатике гигиенические макросы — это макросы гарантированно не приведет к случайному захвату идентификаторов , расширение которых . Они являются особенностью таких языков программирования , как 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))))))

Языки с гигиеническими макросистемами

[ редактировать ]

Гигиеничные макросы обеспечивают безопасность и ссылочную прозрачность за счет того, что преднамеренный захват переменных становится менее простым. Дуг Хойт, автор книги Let Over Lambda , пишет: [16]

Почти все подходы, применяемые для уменьшения влияния захвата переменных, служат только для уменьшения того, что вы можете сделать с помощью defmacro. Гигиенические макросы в лучших ситуациях являются ограждением безопасности для новичка; в худших ситуациях они образуют электрический забор, запирая своих жертв в продезинфицированной и безопасной тюрьме.

Дуг Хойт

Многие гигиенические макросистемы предлагают аварийные люки без ущерба для гарантий, обеспечиваемых гигиеной; например, Racket позволяет вам определять параметры синтаксиса , которые позволяют выборочно вводить связанные переменные. Грегг Хендершотт приводит пример в книге «Страх перед макросами». [17] реализации анафорического оператора if таким способом.

См. также

[ редактировать ]

Примечания

[ редактировать ]
  1. ^ Jump up to: а б Келси, Ричард; Клингер, Уильям; Рис, Джонатан; и др. (август 1998 г.). «Пересмотренный 5 Отчет об алгоритмической языковой схеме» . Высшие порядки и символические вычисления . 11 (1): 7–105. doi : 10.1023/A:1010051815785 .
  2. ^ Фейнберг, Н.; Кин, ЮВ; Мэтьюз, РОД; Withington, PT (1997), Программирование Дилана: объектно-ориентированный и динамический язык , Addison Wesley Longman Publishing Co., Inc.
  3. ^ Jump up to: а б Кольбекер, Э.; Фридман, ДП; Феллейзен, М.; Дуба, Б. (1986). «Гигиеническое макрорасширение» (PDF) . Конференция ACM по LISP и функциональному программированию .
  4. ^ «CLHS: Функция GENSYM» .
  5. ^ «гигиена против генсима» . Community.schemewiki.org . Проверено 11 июня 2022 г.
  6. ^ Костанца, Паскаль; Д'Ондт, Тео (2010). «Внедрение гигиенически совместимых макросов в негигиеническую макросистему» . Журнал универсальной информатики . 16 (2): 271–295. CiteSeerX   10.1.1.424.5218 . дои : 10.3217/jucs-016-02-0271 .
  7. ^ Кольбекер, Э.; Ванд, М. (1987). «Макрос на примере: получение синтаксических преобразований на основе их спецификаций» (PDF) . Симпозиум по основам языков программирования .
  8. ^ Боуден, А.; Рис, Дж. (1988). «Синтаксические замыкания» (PDF) . Лисп и функциональное программирование . Архивировано (PDF) из оригинала 3 сентября 2019 г.
  9. ^ Дыбвиг, К; Хиеб, Р; Бругерман, К. (1993). «Синтаксическая абстракция в схеме» (PDF) . LISP и символьные вычисления . 5 (4): 295–326. дои : 10.1007/BF01806308 . S2CID   15737919 .
  10. ^ Спербер, Майкл; Дибвиг, Р. Кент; Флэтт, Мэтью; Ван Страатен, Антон; и др. (август 2007 г.). «Пересмотренный 6 Отчет о схеме алгоритмического языка (R6RS)» . Руководящий комитет схемы . Проверено 13 сентября 2011 г.
  11. ^ Клингер, Уилл (1991). «Гигиеничные макросы за счет явного переименования». Указатели Lisp ACM SIGPLAN . 4 (4): 25–28. дои : 10.1145/1317265.1317269 . S2CID   14628409 .
  12. ^ Skalski, K.; Moskal, M; Olszta, P, Metaprogramming in Nemerle (PDF) , archived from the original (PDF) on 2012-11-13
  13. ^ «Макросы» .
  14. ^ «Метапрограммирование: язык Джулии» . Архивировано из оригинала 4 мая 2013 г. Проверено 03 марта 2014 г.
  15. ^ «Краткий обзор 6: Подпрограммы» . Архивировано из оригинала 6 января 2014 г. Проверено 3 июня 2014 г.
  16. ^ [1] , Let Over Lambda — 50 лет Lisp Дуг Хойт
  17. ^ [2] , Боязнь макросов
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 976d64f7cf969e46cb16f09f75754593__1715624460
URL1:https://arc.ask3.ru/arc/aa/97/93/976d64f7cf969e46cb16f09f75754593.html
Заголовок, (Title) документа по адресу, URL1:
Hygienic macro - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)