Подтвердить что ты не робот

Примеры макросов Lisp можно использовать для

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

(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)))))))

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

Ответ 4

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

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

7. Макросы: стандартные конструктивные элементы управления


8. Макросы: определение собственных

Ответ 5

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

Ответ 6

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

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

Ответ 7

Только предположение - Языки домена.

Ответ 8

Макросы необходимы для обеспечения доступа к языковым функциям. Например, в 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. Но если у вас нет макросов, вам нужно сделать что-то ужасно неудобное для предоставления доступа к новой функции, которая не является библиотечной функцией, а именно жесткому кодированию некоторых новых правил структуры фразы в синтаксический анализатор.