Примеры того, для чего могут использоваться макросы Lisp - PullRequest
31 голосов
/ 01 апреля 2010

Я слышал, что макросистема Лисп очень мощная. Однако мне трудно найти некоторые практические примеры того, для чего они могут быть использованы; вещи, которые было бы трудно достичь без них.

Кто-нибудь может привести несколько примеров?

Ответы [ 8 ]

40 голосов
/ 02 апреля 2010

преобразования исходного кода. Все виды. Примеры:

  • Новые операторы управления : Вам нужен оператор WHILE? У вашего языка его нет? Зачем ждать, когда доброжелательный диктатор добавит один в следующем году. Напиши это сам. Через пять минут.

  • Сокращенный код : вам нужно двадцать объявлений классов, которые выглядят почти одинаково - различаются только ограниченное количество мест. Напишите форму макроса, которая принимает различия в качестве параметра и генерирует для вас исходный код. Хотите изменить это позже? Поменяйте макрос в одном месте.

  • Замены в дереве исходного кода : Вы хотите добавить код в дерево исходного кода? Переменная действительно должна быть вызовом функции? Оберните макрос вокруг кода, который «обходит» источник и меняет места, где он находит переменную.

  • Синтаксис постфикса : Вы хотите написать свой код в форме постфикса? Используйте макрос, который переписывает код в обычную форму (префикс в Lisp).

  • Эффекты времени компиляции : Вам нужно запустить некоторый код в среде компилятора, чтобы сообщить среде разработки об определениях? Макросы могут генерировать код, который выполняется во время компиляции.

  • Упрощение / оптимизация кода во время компиляции : Вы хотите упростить некоторый код во время компиляции? Используйте макрос, который упрощает работу - таким образом вы можете перенести работу со времени выполнения на время компиляции на основе исходных форм.

  • Генерация кода из описаний / конфигураций : Вам необходимо написать сложное сочетание классов. Например, у вашего окна есть класс, у подпанелей есть классы, между панелями есть ограничения по пространству, у вас есть командный цикл, меню и множество других вещей. Напишите макрос, который захватывает описание вашего окна и его компонентов и создает классы и команды, которые управляют приложением - из описания.

  • Улучшения синтаксиса : Какой-то синтаксис языка выглядит не очень удобным? Напишите макрос, который сделает его более удобным для вас, автор приложения.

  • Языки, специфичные для домена : Вам нужен язык, который ближе к домену вашего приложения? Создайте необходимые языковые формы с кучей макросов.

Мета-лингвистическая абстракция

Основная идея: все, что находится на лингвистическом уровне (новые формы, новый синтаксис, преобразования форм, упрощение, поддержка IDE, ...), теперь может программироваться разработчиком по частям - никакой отдельной стадии обработки макроса.

9 голосов
/ 01 апреля 2010

Выберите любой " инструмент генерации кода ". Прочитайте их примеры. Вот что он может сделать.

За исключением того, что вам не нужно использовать другой язык программирования, поместить любой код расширения макроса, где используется макрос, выполнить отдельную команду для сборки или иметь дополнительные текстовые файлы на жестком диске, которые имеют только ценность к вашему компилятору.

Например, я считаю, что чтения примера Cog должно быть достаточно, чтобы заставить любого программиста на Лиспе плакать.

4 голосов
/ 04 апреля 2010

Что-нибудь, что вы обычно хотели бы сделать в препроцессоре?

Один макрос, который я написал, предназначен для определения конечных автоматов для управления игровыми объектами. Читать код (используя макрос) проще, чем сгенерированный код:

(def-ai ray-ai
  (ground
   (let* ((o (object))
          (r (range o)))
     (loop for p in *players*
           if (line-of-sight-p o p r)
           do (progn
                (setf (target o) p)
                (transit seek)))))
  (seek
   (let* ((o (object))
          (target (target o))
          (r (range o))
          (losp (line-of-sight-p o target r)))
     (when losp
       (let ((dir (find-direction o target)))
         (setf (movement o) (object-speed o dir))))
     (unless losp
       (transit ground)))))

Чем читать:

(progn
 (defclass ray-ai (ai) nil (:default-initargs :current 'ground))
 (defmethod gen-act ((ai ray-ai) (state (eql 'ground)))
            (macrolet ((transit (state)
                         (list 'setf (list 'current 'ai) (list 'quote state))))
              (flet ((object ()
                       (object ai)))
                (let* ((o (object)) (r (range o)))
                  (loop for p in *players*
                        if (line-of-sight-p o p r)
                        do (progn (setf (target o) p) (transit seek)))))))
  (defmethod gen-act ((ai ray-ai) (state (eql 'seek)))
            (macrolet ((transit (state)
                         (list 'setf (list 'current 'ai) (list 'quote state))))
              (flet ((object ()
                       (object ai)))
                (let* ((o (object))
                       (target (target o))
                       (r (range o))
                       (losp (line-of-sight-p o target r)))
                  (when losp
                    (let ((dir (find-direction o target)))
                      (setf (movement o) (object-speed o dir))))
                  (unless losp (transit ground)))))))

Инкапсулируя всю генерацию конечного автомата в макрос, я также могу гарантировать, что я ссылаюсь только на определенные состояния и предупреждаю, если это не так.

3 голосов
/ 02 апреля 2010

R , стандартный язык программирования статистики, имеет макросы (руководство R, глава 6 ). Это можно использовать для реализации функции lm(), которая анализирует данные на основе модели, которую вы указываете в качестве кода.

Вот как это работает: lm(Y ~ aX + b, data) попытается найти a и b параметры, которые наилучшим образом соответствуют вашим данным. Крутая часть, вы можете заменить любое линейное уравнение на aX + b, и оно все равно будет работать. Это замечательная функция, которая упрощает вычисление статистики, и работает только так элегантно, потому что lm() может анализировать заданное уравнение, что и делают макросы Lisp.

3 голосов
/ 02 апреля 2010

Помимо расширения синтаксиса языка, чтобы вы могли выразить себя более четко, он также дает вам контроль над оценкой. Попробуйте написать свой собственный if на выбранном вами языке, чтобы вы могли написать my_if something my_then print "success" my_else print "failure" и не выполнять оценку обоих операторов печати. На любом строгом языке без достаточно мощной макросистемы это невозможно. Тем не менее, ни один программист на Common Lisp не сочтет эту задачу слишком сложной. То же самое для for -циклов, foreach циклов и т. Д. Вы не можете выразить эти вещи в C, потому что они требуют специальной семантики оценки (люди действительно пытались ввести foreach в Objective-C, но это не сработало хорошо), но они почти тривиальны в Common Lisp из-за его макросов.

3 голосов
/ 01 апреля 2010

С помощью макросов вы можете определить свой собственный синтаксис, таким образом, вы расширяете Lisp и делаете его
подходит для программ, которые вы пишете.

Ознакомьтесь с очень хорошей онлайновой книгой Практический Common Lisp , для практических примеров.

7. Макросы: стандартные управляющие конструкции
8. Макросы: определение собственного

2 голосов
/ 01 апреля 2010

Просто предположение - доменные языки.

1 голос
/ 18 декабря 2015

Макросы необходимы для обеспечения доступа к языковым функциям. Например, в TXR Lisp у меня есть одна функция с именем sys:capture-cont для захвата продолжения с разделителями. Но это неудобно использовать само по себе. Таким образом, вокруг него обернуты макросы, такие как suspend или obtain и yield, которые предоставляют альтернативные модели для возобновляемого, приостановленного выполнения. Они реализованы здесь .

Другим примером является сложный макрос defstruct, который предоставляет синтаксис для определения типа структуры. Он компилирует свои аргументы в lambda -s и другой материал, который передается в функцию make-struct-type. Если бы программы использовали make-struct-type непосредственно для определения ООП-структур, они были бы ужасны:

1> (macroexpand '(defstruct foo bar x y (z 9) (:init (self) (setf self.x 42))))
(sys:make-struct-type 'foo 'bar '()
                      '(x y z) ()
                      (lambda (#:g0101)
                        (let ((#:g0102 (struct-type #:g0101)))
                          (unless (static-slot-p #:g0102 'z)
                            (slotset #:g0101 'z
                                     9)))
                        (let ((self #:g0101))
                          (setf (qref self x)
                           42)))
                      ())

Хлоп! Многое должно быть правильно. Например, мы не просто вставляем 9 в слот z, потому что (из-за наследования) мы можем фактически быть базовой структурой производной структуры, а в производной структуре z может быть статическим слотом (делится на экземпляры). Мы бы забили значение, установленное для z в производном классе.

В ANSI Common Lisp хорошим примером макроса является loop, который обеспечивает полный язык для параллельной итерации. Один loop вызов может выражать целый сложный алгоритм.

Макросы позволяют нам независимо думать о синтаксисе, который нам нужен в языковой функции, и об основных функциях или специальных операторах, необходимых для его реализации. Независимо от того, какой выбор мы сделаем в этих двух, макросы соединят их для нас. Мне не нужно беспокоиться, что make-struct уродливо использовать, поэтому я могу сосредоточиться на технических аспектах; Я знаю, что макрос может выглядеть одинаково независимо от того, как я делаю различные компромиссы. Я принял решение о проектировании, что вся инициализация структуры будет выполняться некоторыми функциями, зарегистрированными для типа. Хорошо, это означает, что мой макрос должен взять все инициализации в синтаксисе определения слота и скомпилировать анонимные функции, где инициализация слота выполняется кодом, сгенерированным в теле.

Макросы - это компиляторы для битов синтаксиса, для которых функции и специальные операторы являются целевым языком.

Иногда люди (обычно не являющиеся пользователями Lisp) критикуют макросы следующим образом: макросы не добавляют никаких возможностей, только синтаксический сахар.

Во-первых, синтаксический сахар - это возможность.

Во-вторых, вы также должны рассматривать макросы с «общей точки зрения хакера»: комбинировать макросы с работой на уровне реализации. Если я добавляю функции к диалекту Lisp, такие как структуры или продолжения, я фактически расширяю возможности. Участие макросов в этом предприятии необходимо . Хотя макросы не являются источником новой силы (он не исходит от самих макросов), они помогают приручить и использовать его, придав ему выражение.

Если у вас нет sys:capture-cont, вы не можете просто взломать его поведение с помощью макроса suspend. Но если у вас нет макросов, вы должны сделать что-то ужасно неудобное, чтобы предоставить доступ к новой функции, которая не является библиотечной функцией, а именно жестко запрограммировать некоторые новые правила структуры фраз в парсер .

...