Есть ли простой пример для объяснения макросов Lisp «универсальному» программисту? - PullRequest
6 голосов
/ 30 декабря 2010

Недавно я разговаривал с коллегой и пытался рассказать ему о красоте (общего) Лиспа.Я пытался как-то объяснить макросы, так как считаю макросы одной из самых убойных возможностей Лиспа, но у меня не получилось с треском провалиться - я не смог найти хороший пример, который был бы коротким, лаконичным и понятным для «простого смертного»"программист (десятилетие опыта работы с Java, в целом умный парень, но очень мало опыта работы с языками" высшего порядка ").

Как бы вы объяснили макросы Lisp на собственном примере, если бы вам пришлось?

Ответы [ 6 ]

8 голосов
/ 30 декабря 2010

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

<common code>
<specific code> 
<other common code>

, где <common code> всегда одинаково.Вот несколько примеров такой схемы:

1.Макрос time. Код на языке без макросов будет выглядеть примерно так:

int startTime = getCurrentTime();
<actual code>
int endTime = getCurrentTime();
int runningTime = endTime - startTime; 

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

(time 
  <actual code>) 

2.Транзакции. Попросите Java-программиста написать метод для простого SELECT с JDBC - это займет 14-17 строк и будет включать в себя код для открытия соединения и транзакции, чтобы закрыть их, несколько вложенных операторов try-catch-finally и только 1 или2 строки уникального кода.
В Лиспе вы просто пишете макрос with-connection и уменьшаете код до 2-3 строк.

3.Синхронизация. ОК, Java, C # и большинство современных языков уже имеют операторы для этого, но что делать, если у вашего языка нет такой конструкции?Или если вы хотите ввести новый вид синхронизации, например транзакции на основе STM ?Опять же, вы должны написать отдельный класс для этой задачи и работать с ним вручную, т.е. поместить общий код вокруг каждого оператора, который вы хотите синхронизировать.

Это было всего несколько примеров.Вы можете упомянуть «незабываемые» макросы, такие как with-open series, которые очищают среду и защищают вас от утечек ресурсов, новые конструктивные макросы, такие как cond вместо множества if s, и, конечно же, donне забывайте о ленивых конструкциях, таких как if, or и and, которые не оценивают свои аргументы (в отличие от применения процедуры).

Некоторые программисты могут утверждать, что в их языке есть технология для обработки того или иного случая (ORM, AOP и т. Д.), Но спрашивают их, нужны ли все эти технологии, если бы существовали макросы?

Итак, взяв все это и отвечая на оригинальный вопрос о том, как объяснить макросы.Возьмите любой широко используемый код на Java (C #, C ++ и т. Д.), Преобразуйте его в Lisp, а затем перепишите его как макрос.

6 голосов
/ 30 декабря 2010

Новый оператор WHILE

Ваш языковой дизайнер забыл оператор WHILE.Вы отправили его по почте несколько раз.Нет успехаВы ждали от языковой версии 2.5, 2.6 до 3.0.Ничего не произошло ...

В Лиспе:

(defmacro while ... вставьте здесь свою реализацию while ...)

Готово.

Тривиальная реализация с использованием LOOP занимает минуту.

Генерация кода по спецификациям

Затем вы можете захотеть проанализировать подробные записи вызовов (CDR).У вас есть имена записей с описаниями полей.Теперь я могу написать классы и методы для каждого из них.Я также мог бы придумать какой-нибудь формат конфигурации, разобрать файл конфигурации и создать классы.В Лиспе я написал бы макрос, который генерирует код из компактного описания.

См. Специфичные для домена языки в Лиспе , скриншот, показывающий типичный цикл разработки от рабочего эскиза до простого макросана основе обобщения.

Перезапись кода

Представьте, что вам нужно получить доступ к слотам объектов, используя функции получения.Теперь представьте, что вам нужно обращаться к некоторым объектам несколько раз в некоторой области кода.По какой-то причине использование временных переменных не является решением.

...
... (database-last-user database) ...
...

Теперь вы можете написать макрос WITH-GETTER, который вводит символ для выражения getter.

(with-getters (database (last-user database-last-user))
   ...
   ... last-user
   ...)

Макрос переписал быисточник внутри заключенного блока и замените все указанные символы выражением-получателем.

3 голосов
/ 31 декабря 2010

Поскольку конкретные примеры могут увязнуть в деталях языка, на котором вы их пишете, рассмотрим не конкретное, но соответствующее утверждение:

«Вы знаете весь этот стандартный код, который вам иногда приходится писать? Вам никогда не нужно писать стандартный шаблон на lisp, поскольку вы всегда можете написать генератор кода, чтобы сделать это за вас».

Под «шаблоном» я имею в виду одноразовые реализации интерфейса в Java, переопределение неявных конструкторов в c ++, написание пар get () - set () и т. Д. Я думаю, что эта риторическая стратегия может работать лучше, чем пытаться объяснять макросы непосредственно слишком подробно, так как он, вероятно, слишком хорошо знаком с различными типами шаблонов, а макрос никогда не видел.

1 голос
/ 30 декабря 2010

Я недостаточно хорошо знаю CL, но подойдут ли макросы Scheme?Вот цикл while в Scheme:

(define-syntax while
  (syntax-rules ()
    ((while pred body ...)
     (let loop ()
       (if pred (begin body ... (loop)))))))

В этом случае пример демонстрирует, что вы можете легко написать свои собственные управляющие структуры, используя макросы.foof-loop представляет собой набор еще более полезных циклических конструкций (вероятно, ничего нового, если учесть CL, но все же хорошо для демонстрации).


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

(define-syntax let-assq
  (syntax-rules ()
    ((let-assq alist (key) body ...)
     (let ((key (assq-ref alist 'key)))
       body ...))
    ((let-assq alist (key rest ...) body ...)
     (let ((key (assq-ref alist 'key)))
       (let-assq alist (rest ...) body ...)))))

;; Guile built-in
(define (assq-ref alist key)
  (cond ((assq key alist) => cdr)
        (else #f)))

Пример использования:

(define (binary-search tree needle (lt? <))
  (let loop ((node tree))
    (and node
         (let-assq node (value left right)
           (cond ((lt? needle value) (loop left))
                 ((lt? value needle) (loop right))
                 (else value))))))

Обратите внимание, как макрос let-assq позволяет выбрать value, leftи right ключи от "узла" без необходимости писать гораздо более длинную let форму.

0 голосов
/ 30 декабря 2010

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

0 голосов
/ 30 декабря 2010

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

Мой любимый пример - анафорические макросы. Как aif, некоторое время или aand:

  (defmacro aif (test-form then-form &optional else-form)
  `(let ((it ,test-form))
     (if it ,then-form ,else-form)))

  (defmacro awhile (expr &body body)
      `(do ((it ,expr ,expr)) ((not it))
         ,@body))

    (defmacro aand (&rest args)
      (cond 
        ((null args) t)
        ((null (cdr args)) (car args))
        (t `(aif ,(car args) (aand ,@(cdr args))))))

Это очень просто и может сэкономить много печатать.

...