Определение функции defmacro с использованием только примитивов LISP?

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

, cond, lambda, label

на этой основе мы назовем эти "примитивы LISP" (хотя я открыт для аргумента о предикатах типа, таких как numberp)

как бы вы определили

2 ответов


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

но это не мешает нам переписать наш код во время выполнения на машине Маккарти. Фокус в том, чтобы процитировать аргументы, которые мы передаем нашим "макросам", чтобы они не оценивались.

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

некоторые примеры (в синтаксисе 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

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

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


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

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

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

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

Если вы действительно хотите увидеть волосатые детали, прочитайте структура и интерпретация компьютерных программ или читать Квиннека шепелявить в Малом Кусочки!--11-->.