Let Over Lambda - 50 Years of Lisp / Let Over Lambda - 50 лет Lisp-у: Глава 6 - Анафорические Макросы

← →
Английский оригинал Перевод на русский

Сожалеем, но текст оригинала доступен только зарегистрированным пользователям.

6.1 Больше Фор?

Charlz_Klug 10.02.14 в 04:37

0

Анафорические макросы (Anaphoric macros) - это одни из наиболее интересных макросов из книги Пола Грэма On Lisp. Анафорический макрос - это макрос осуществляющий преднамеренный захват переменной из форм, переданных в макрос. Благодаря наличию прозрачных спецификаций (transparent specifications) эти преднамеренно захваченные переменные порождают окна, позволяющие осуществлять контроль над расширением макроса. Через эти окна мы можем манипулировать расширением с помощью комбинаций (combinations).

Charlz_Klug 10.02.14 в 04:46

0

Классическая анафора, такая как в On Lisp, названа также, как и дословное 1 анафор (anaphor) и во множественном числе, анафора (anaphora). Анафор обозначает захват свободного слова U-Языка (free U-Language word) для использования в последующем U-Языке. В терминах программирования, реализация классической анафоры означает нахождение места в вашем коде - или в коде, который вы хотите написать - где выражения могли бы извлечь выгоду, будучи способными сослаться на результаты предыдущих, связанных выражений. Настоятельно рекомендуется к изучению анафора Грэма и код, связанный с этой анафорой. Особенно обратите внимание на макрос defanaph, позволяющий использовать некоторые интересные типы автоматического анафора (automatic anaphor).

Charlz_Klug 10.02.14 в 05:27

0

1 Цитирование U-Языка.

Charlz_Klug 10.02.14 в 05:59

0

По истечении некоторого периода было обнаружено, что alambda - это наиболее полезный анафорический макрос в On Lisp. К тому же alambda - это один из наиболее простых и элегантных примеров, демонстрирующих анафорический макрос и преднамеренный захват переменной.

Charlz_Klug 10.02.14 в 06:11

0

Листинг 6.1: ALAMBDA
;; alambda Грэма
(defmacro alambda (parms &body body)
`(labels ((self ,parms ,@body))
#'self))

Charlz_Klug 10.02.14 в 10:15

0

С alambda мы захватываем имя self, таким образом, мы можем использовать его для построения очень анонимной функции. Другими словами рекурсия становится такой же простой как и вызов self. Например, следующая функция возвращает список 2 чисел с n до 1:

Charlz_Klug 10.02.14 в 10:18

0

2 Если условие возвращает ложное значение, то отсутствующий третий элемент if формы вернёт nil, что в свою очередь является списком.

Charlz_Klug 10.02.14 в 10:24

0

(alambda (n)
(if (> n 0)
(cons
n
(self (- n 1)))))

Charlz_Klug 10.02.14 в 10:25

0

Alambda делает наш код более интуитивным и простым для чтения, позволяя нам думать об анонимных функциях как о функциях способных вызывать саму себя с той же лёгкостью, как и добавление единственной буквы 3. Поскольку alambda прозрачно специфицирован для self привязки - и тот факт что единственная причина использования alambda - это применение этой привязки - то нежелательный захват переменной уже не проблема.

Charlz_Klug 10.02.14 в 10:34

0

3 Это другая причина не шарп-закавычивать лямбда формы. Изменение шарп-закавыченных лямбда форм в alambda формы также потребует удаления двух символов.

Charlz_Klug 10.02.14 в 10:36

0

Другой удобный анафорический макрос из On Lisp - это aif, макрос, привязывающий результат проверки к it при положительном (вторичном) случае для последующего применения 4. Aif использует удобную COMMON LISP особенность: обобщённые булевы значения (generalised booleans). В COMMON LISP, все не-nil значения являются истинными значениями, поэтому COMMON LISP программисты обычно встраивают интересную информацию в истинные значения. Языки в которых зарезервированы истинные и ложные значения - в частности Scheme - используют явные булевы значения (explicit booleans), которые иногда заставляют вас выводить дополнительную информацию для удовлетворения конструкций с избыточным типом. В Scheme добавлен костыль (kludge) для того, чтобы if, cond, and, or и do работали с не-булевыми 5 значениями [R5RS-P25]. Конечно, COMMON LISP спроектирован правильно - всё является булевым значением.

Charlz_Klug 10.02.14 в 11:13

0

4 Упражнение: Почему ложный (третичный или альтернативный) вариант никогда не использует эту анафору?

Charlz_Klug 10.02.14 в 11:18

0

5 С помощью Scheme предиката boolean?.

Charlz_Klug 10.02.14 в 11:20

0

Листинг 6.2: AIF
;; aif Грэма
(defmacro aif (test then &optional else)
`(let ((it ,test))
(if it ,then ,else)))

Charlz_Klug 11.02.14 в 03:25

0

Также следует отметить что aif и alambda, как и все анафорические макросы, нарушают лексическую прозрачность. Или иначе, выражаясь модными словами, можно сказать что они не гигиеничные (unhygienic) макросы. Они, как и большое количество макросов в этой книге, вводят невидимые лексические привязки и поэтому не могут быть созданы в макро системах со строгим соблюдением гигиены. Даже большое количество Scheme систем, платформа в которой много экспериментировали с гигиеной, предоставляют макросы в негигиеничном defmacro стиле - возможно, по той причине, что даже разработчики Scheme не воспринимали всерьёз гигиену. Подобно обучению катания на велосипеде, гигиенические системы в своём большинстве игрушки, которые забрасывают после овладевания минимальным уровнем мастерства.

Charlz_Klug 11.02.14 в 04:59

0

Да, существует много интересных вещей, которые мы можем делать с помощью преднамеренного захвата переменной. Существует множество фор (phors). Эта книга и книга Грэма On Lisp описывает лишь малую часть возможностей, заложенных в эту технику. Из грамотного применения анафорических макросов можно извлечь много новых потрясающих изобретений.

Charlz_Klug 11.02.14 в 05:09

0

После краткой беседы об анафорах, продемонстрированной считывающими макросами, остаток этой главы описывает узкое приложение анафор к одной из центральных тем этой книги: лексические замыкания - let, окружающий lambda. Большая часть этой главы посвящена интересным анафорическим макросам, предназначенным для изменения, адаптации и расширении замыканий. И хотя данная тема очень удобна в практическом применение для написания кода, мы используем её как платформу для обсуждения возможностей и вариантов анафорических макросов. Применение макросов для расширения концепции замыканий - это, на данный момент, горячо исследуемая тема [FIRST-CLASS-EXTENTS] [OPENING-CLOSURES].

Charlz_Klug 11.02.14 в 06:39

0

6.2 Шарп-Закавычивание

Charlz_Klug 11.02.14 в 11:05

0

6.2 Шарп-Обратное Закавычивание

Charlz_Klug 12.02.14 в 10:39

0

Хотя большинство анафор представлено в виде обычных макросов, у считывающих макросов тоже есть возможность использования кода, создающего для нас невидимые привязки. Такие считывающие макросы называются считывающими анафорами (read anaphora). Этот раздел ознакомит вас с таким макросом, который являясь весьма незатейливым, удивил даже меня и который стал одним из наиболее полезных макросов, используемых в этой книге. Я постарался как можно скорее перейти к этому макросу, поскольку он будет использоваться в оставшемся коде. Более того, несколько рассмотренных нами макросов уже используют его.

Charlz_Klug 11.02.14 в 11:17

0

Листинг 6.3: SHARP-BACKQUOTE
(defun |#`-reader| (stream sub-char numarg)
(declare (ignore sub-char))
(unless numarg (setq numarg 1))
`(lambda ,(loop for i from 1 to numarg
collect (symb 'a i))
,(funcall
(get-macro-character #\`) stream nil)))

(set-dispatch-macro-character
#\# #\` #'|#`-reader|)

Charlz_Klug 12.02.14 в 04:47

0

Шарп-закавычивание - это считывающий макрос, выполняющий считывание как лямбда форма. По умолчанию, эта лямбда форма получает только один аргумент: a1. Затем считывающий макрос рекурсивно вызывает считывающую функцию для переданного потока. Ниже есть пример с остановленным вычислением (с помощью quote), и мы можем рассмотреть прозрачное введение считывающей анафоры 6:

Charlz_Klug 11.02.14 в 11:23

0

Шарп-обратное закавычивание - это считывающий макрос, выполняющий считывание как лямбда форма. По умолчанию, эта лямбда форма получает только один аргумент: a1. Затем считывающий макрос рекурсивно вызывает считывающую функцию для переданного потока. Ниже есть пример с остановленным вычислением (с помощью quote), и мы можем рассмотреть прозрачное введение считывающей анафоры 6:

Charlz_Klug 12.02.14 в 10:39

0

6 Конечно, префикс захваченного символа, "a", расшифровывается анафорой.

Charlz_Klug 12.02.14 в 04:52

0

* '#`((,a1))

(LAMBDA (A1)
`((,A1)))

Charlz_Klug 12.02.14 в 04:52

0

Этот считывающий макрос формирует абстракцию общего шаблона макроса. Например, если у нас есть список переменных и мы хотим создать список let привязок, где каждая переменная привязана к символу, скажем, empty, то мы можем использовать mapcar следующим способом:

Charlz_Klug 12.02.14 в 04:59

0

* (mapcar (lambda (a)
(list a ''empty))
'(var-a var-b var-c))

((VAR-A 'EMPTY)
(VAR-B 'EMPTY)
(VAR-C 'EMPTY))

Charlz_Klug 12.02.14 в 04:59

0

Но особенно для сложных списковых структур, такой подход может оказаться довольно грязным, поэтому лисп программисты предпочитают использовать обратное закавычивание для поднятия на один уровень закавычивания:

Charlz_Klug 12.02.14 в 05:11

0

* (mapcar (lambda (a)
`(,a 'empty))
'(var-a var-b var-c))

((VAR-A 'EMPTY)
(VAR-B 'EMPTY)
(VAR-C 'EMPTY))

Charlz_Klug 12.02.14 в 05:11

0

Наш новый считывающий макрос вводит анафору и скрывает лямбда форму:

Charlz_Klug 12.02.14 в 05:12

0

* (mapcar #`(,a1 'empty)
'(var-a var-b var-c))

((VAR-A 'EMPTY)
(VAR-B 'EMPTY)
(VAR-C 'EMPTY))

Charlz_Klug 12.02.14 в 05:12

0

1 в символе a1 выше говорит о том, что пользователи считывающего макроса могут вводить переменное число анафор, зависящее от числа переданного в параметр numarg считывающего макроса:

Charlz_Klug 12.02.14 в 06:13

0

* '#2`(,a1 ,a2)

(LAMBDA (A1 A2)
`(,A1 ,A2))

Charlz_Klug 12.02.14 в 06:13

0

Таким образом мы можем одновременно заmapcar-ить шарп-закавыченные выражения с несколькими списками:

Charlz_Klug 12.02.14 в 06:46

0

Таким образом мы можем одновременно заmapcar-ить шарп-обратно закавыченные выражения с несколькими списками:

Charlz_Klug 12.02.14 в 10:40

0

* (let ((vars '(var-a var-b var-c)))
(mapcar #2`(,a1 ',a2)
vars
(loop for v in vars
collect (gensym
(symbol-name v)))))

((VAR-A '#:VAR-A1731)
(VAR-B '#:VAR-B1732)
(VAR-C '#:VAR-C1733))

Charlz_Klug 12.02.14 в 08:44

0

Ещё один способ думать о шарп-обратном закавычивании: шарп-обратное закавычивание выполняет интерполяцию списка, также, как функция format выполняет интерполяцию строки. Format позволяет нам использовать шаблоны со слотами, заполняемыми значениями отдельных аргументов, аналогично шарп-обратное закавычивание позволяет нам отделить структуру интерполированного списка от значений, с которыми мы хотим их объединить. Благодаря ранее описанному дуализму синтаксиса между лямбда формами в позиции функции в списке и лямбда формами, использующими макрос lambda для расширения в функцию, мы также можем использовать шарп-обратное закавычивание в роли первого элемента при вызове функции:

Charlz_Klug 12.02.14 в 10:38

0

* (#3`(((,a1)) ,@a2 (,a3))
(gensym)
'(a b c)
'hello)

(((#:G1734)) A B C (HELLO))

Charlz_Klug 13.02.14 в 08:48

0

В отличие от format шарп-обратное закавычивание не использует последовательное позиционирование. Вместо этого используется числа для наших анафорических привязок. Вследствие этого порядок может перемешиваться и мы можем сращивать привязки более одного раза:

Charlz_Klug 13.02.14 в 11:15

0

* (#3`(((,@a2)) ,a3 (,a1 ,a1))
(gensym)
'(a b c)
'hello)

(((A B C)) HELLO (#:G1735 #:G1735))

Charlz_Klug 13.02.14 в 11:15

0

Упражнение: Ссылки на gensym #:G1735 выглядят так, как будто ссылаются на один и тот-же символ, но, конечно, вы никогда не можете утверждать это глядя на напечатанные имена. Являются ли эти символы eq-абельными? Обоснуйте ответ.

Charlz_Klug 13.02.14 в 11:26

0

6.3 Alet и Машины с Конечным Состоянием.

Charlz_Klug 14.02.14 в 10:34

0

С lambda и if возможна только одна полезная анафорическая конфигурация. Но большинство интересных типов анафорических макросов используют расширения самыми неожиданными способами. Этот раздел - как и большая часть этой главы - посвящена одному из таких макросов: alet. Какие дополнительные привязки могут быть полезны для форм внутри тела let формы? Let используется для того, чтобы создать привязки к переданным переменным. Однако, макро улучшение let даст возможность получения доступа ко всем переданным формам, даже если тела этих выражений должны вычисляться с новыми привязками. Так какая же часть тела наиболее полезна? В большинстве случаев это последняя форма тела, поскольку результаты этой формы будут возвращены из самого оператора let 7. Мы уже видели что когда мы возвращаем лямбда выражение, ссылающееся на привязки, созданные let-ом, то результатом является лексическое замыкание - объект, часто сохраняемый и используемый для последующего доступа к переменным в let операторе. Таким образом, расширение нашего аналога объекта-замыкания, макрос alet% работает также, как и специальная форма let, с тем отличием, что захватывает символ this из тела и привязывает его к последнему выражению в теле формы - единственное, что будет возвращено как замыкание 8.

Charlz_Klug 14.02.14 в 11:09

0

7 Поскольку let предоставляет неявный progn.

Charlz_Klug 14.02.14 в 11:09

0

8 Setq применяется потому что форма привязанная к this определена в лексической области видимости других аргументов заданных через letargs.

Charlz_Klug 14.02.14 в 11:12

0

Листинг 6.4: ALET-1
(defmacro alet% (letargs &rest body)
`(let ((this) ,@letargs)
(setq this ,@(last body))
,@(butlast body)
this))

Charlz_Klug 14.02.14 в 11:13

0

Alet% может быть полезен в случае когда у нас есть инициализирующий код в лямбда форме, который мы не хотим дублировать. Поскольку this привязан к возвращаемой лямбда форме, то у нас есть возможность выполнить его перед выходом из окружающего let. Следующий пример - это замыкание, конструкция которого показывает простой пример использования alet% с целью избежания дублирования инициализирующего и сбрасывающего кода:

Charlz_Klug 14.02.14 в 11:20

0

* (alet% ((sum) (mul) (expt))
(funcall this :reset)
(dlambda
(:reset ()
(psetq sum 0
mul 1
expt 2))
(t (n)
(psetq sum (+ sum n)
mul (* mul n)
expt (expt expt n))
(list sum mul expt))))

#<Interpreted Function>

Charlz_Klug 14.02.14 в 11:20

0

С помощью этого кода мы можем последовательно изменять значения sum, mul и expt:

Charlz_Klug 17.02.14 в 02:53

0

* (loop for i from 1 to 5 collect (funcall * 2))

((2 2 4)
(4 4 16)
(6 8 256)
(8 16 65536)
(10 32 4294967296))

Charlz_Klug 17.02.14 в 02:56

0

Мы можем сбросить замыкание вызвав метод :reset. Заметьте, что благодаря alet%, для сброса в базовые состояния (0 для sum, 1 для mul и 2 для expt) достаточно использовать :reset в одном месте:

Charlz_Klug 17.02.14 в 03:04

0

* (funcall ** :reset)

NIL

Charlz_Klug 17.02.14 в 03:04

0
Минутку...

Следующая страница →