Определение функции defmacro с использованием только примитивов LISP? - PullRequest
11 голосов
/ 21 августа 2010

Маккарти Элементарными S-функциями и предикатами были atom, eq, car, cdr, cons

Затем он добавил к своей основной записи, чтобы дать возможность написать то, что он назвал S-функциями: quote, cond, lambda, label

Исходя из этого, мы будем называть эти «примитивы LISP» (хотя я открыт для аргумента о предикатах типа, таких как numberp)

Как бы вы определили функцию defmacro, используя только эти примитивы в LISP по вашему выбору? (включая Схему и Clojure)

Ответы [ 2 ]

5 голосов
/ 21 августа 2010

Проблема с попыткой сделать это на компьютере, подобном машине LISP Маккарти, заключается в том, что нет способа предотвратить вычисление аргумента во время выполнения, и нет способа изменить ситуацию во время компиляции (что и делают макросы: в основном они переставляют код перед его компиляцией).

Но это не мешает нам переписывать наш код во время выполнения на машине Маккарти. Хитрость заключается в том, чтобы процитировать аргументы, которые мы передаем нашим «макросам», чтобы они не оценивались.

В качестве примера давайте рассмотрим функцию, которую мы могли бы хотеть иметь; unless. Наша теоретическая функция принимает два аргумента, p и q, и возвращает q , если p не является истинным. Если p истинно, вернуть ноль.

Некоторые примеры (в синтаксисе Clojure, но это ничего не меняет):

(unless (= "apples" "oranges") "bacon")
=> "bacon"

(unless (= "pears" "pears") "bacon")
=> nil

Итак, сначала мы могли бы написать unless как функцию:

(defn unless [p q]
    (cond p nil
          true q))

И это, кажется, работает нормально:

(unless true 6)
=> nil

(unless false 6)
=> 6

А с LISP Маккарти это будет работать просто отлично. Проблема в том, что в нашем современном Лиспе нет не имеющего побочных эффектов кода, поэтому факт, что все аргументы, переданные в unless, оцениваются независимо от того, хотим мы этого или нет, проблематичен. На самом деле, даже в LISP Маккарти это могло быть проблемой, если бы, скажем, для оценки одного из аргументов потребовалось возраст , а мы бы хотели делать это редко. Но это особенно проблема с побочными эффектами.

Итак, мы хотим, чтобы наши unless оценивали и возвращали q только , если p ложно. Этого мы не сможем сделать, если передадим q и p в качестве аргументов функции.

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

Итак, у нас есть новый unless:

(defn unless [p q] 
    (cond (eval p) nil 
          true (eval q)))

И мы используем его немного по-другому:

(unless (quote false) (quote (println "squid!")))
=> "squid" nil
(unless (quote true) (quote (println "squid!")))
=> nil

И у вас есть то, что можно щедро назвать макросом.


Но это не defmacro или эквивалент в других языках. Это потому, что на машине Маккарти не было способа выполнить код во время компиляции. И если вы оценивали свой код с помощью функции eval, он не мог знать, что не нужно оценивать аргументы для «макро» -функции. Между чтением и оценкой не было такого различия, как сейчас, хотя идея была в этом. Была возможность «переписать» код, в кругу quote и в операциях со списком вместе с eval, но он не был интернирован в языке, как сейчас (я бы назвал его синтаксическим почти, просто: просто процитируй свои аргументы, и ты получишь силу макросистемы.)

Надеюсь, я ответил на ваш вопрос, не пытаясь определить приличных defmacro с этими примитивами сам. Если вы действительно хотите это увидеть, я бы указал вам на источник с трудом для подхвата для defmacro в источнике Clojure или Google вокруг него.

2 голосов
/ 21 августа 2010

Полное объяснение его во всех деталях потребовало бы очень много места и времени для ответа здесь, но план действительно довольно прост. Каждый LISP в конечном итоге имеет в своей основе что-то вроде цикла READ-EVAL-PRINT, то есть что-то, что принимает список, элемент за элементом, интерпретирует его и изменяет состояние - либо в памяти, либо путем печати результата.

Часть чтения просматривает каждый прочитанный элемент и что-то с ним делает:

(cond ((atom elem)(lambda ...))
      ((function-p elem) (lambda ...)))

Чтобы интерпретировать макросы, вам просто (?) Нужно реализовать функцию, которая помещает текст шаблона макроса где-то в хранилище, предикат для этого цикла repl - что означает простое определение функции - которая говорит: «О, это макрос! ", а затем скопируйте этот текст шаблона обратно в читатель, чтобы он интерпретировался.

Если вы действительно хотите увидеть подробности, прочитайте Структура и интерпретация компьютерных программ или прочитайте Lisp от Queinnec в Small PIeces .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...