Является ли оценка построенной оценки равной макросу?

Я хочу знать, равны ли эти два определения nth:

I. определяется как макрос:

(defmacro -nth (n lst)
  (defun f (n1 lst1)
    (cond ((eql n1 0) lst1)
      (t `(cdr ,(f (- n1 1) lst1)))))
  `(car ,(f n lst)))

II. определяется как набор функций:

(defun f (n lst)
  (cond ((eql n 0) lst)
        (t `(cdr ,(f (- n 1) lst)))))
(defun f1 (n lst)
  `(car ,(f n `',lst)))
(defun --nth (n lst)
  (eval (f1 n lst)))

Я правильно понимаю? Является ли определение макроса вычислением выражения, построенного в его теле?


person Gorozhin    schedule 04.06.2014    source источник


Ответы (2)


Хорошо, давайте начнем с самого начала.

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

Итак, внутри макроса есть несколько уровней кода.

  • Код без кавычек будет оцениваться во время макрорасширения (не во время выполнения!), в вашем примере вы определяете функцию f при раскрытии макроса (для чего?);
  • Далее здесь цитируется (с обычными кавычками или обратными кавычками или даже вложенными обратными кавычками) код, который станет частью результата макрорасширения (в буквальном виде); вы можете контролировать, какая часть кода будет оцениваться во время макрорасширения, а какая останется неизменной (в кавычках, частично или полностью). Это позволяет создать что-либо до того, как оно будет выполнено.

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

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

Вы можете использовать его следующим образом:

CL-USER> (defparameter *x* '((a . 1) (b . 2) (c . 3) (d . 4)))
*X*
CL-USER> (aif (find 'c *x* :key #'car) (1+ (cdr it)) 0)
4

Этот макрос создает полезную лексическую привязку, захватывая переменную it. После проверки условия не нужно пересчитывать результат, он доступен в формах «тогда» и «иначе». Это невозможно сделать только с помощью функции, она ввела в язык новую конструкцию управления. Но макрос — это не только создание лексического окружения.

Макрос — мощный инструмент. Невозможно полностью описать, что вы можете с ним делать, потому что вы можете делать все. Но nth — это не то, для чего вам нужен макрос. Чтобы создать клон nth, вы можете попробовать написать рекурсивную функцию.

Важно отметить, что макрос LISP — самая мощная вещь в мире программирования, а LISP — единственный язык, обладающий такой мощью ;-)

Чтобы вдохновить вас, я бы порекомендовал эту статью: http://www.paulgraham.com/avg.html

Чтобы освоить макрос, начните примерно так:

http://www.gigamonkeys.com/book/macros-defining-your-own.html

Затем может быть "On Lisp" Пола Грэма, затем "Let Over Lambda".

person Mark Karpov    schedule 04.06.2014

Нет необходимости ни в макросе, ни в eval для создания абстракций для получения n-го элемента списка. Ваш макрос -nth даже не работает, если индекс не является буквальным числом. попробуй это:

(defparameter test-list '(9 8 7 6 5 4 3 2 1 0))
(defparameter index 3)

(nth index test-list)  ; ==> 6 (this is the LISP provided nth)
(-nth index test-list) ; ==> ERROR: index is not a number

Типичное рекурсивное решение n-й:

(defun nth2 (index list)
  (if (<= index 0) 
      (car list)
      (nth2 (1- index) (cdr list))))

(nth2 index test-list)  ; ==> 6 

Типичный вариант петли

(defun nth3 (index list)
  (loop :for e :in list
        :for i :from index :downto 0
        :when (= i 0) :return e))

(nth3 index test-list)  ; ==> 6 

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

eval никогда не следует использовать, если в этом нет особой необходимости. Обычно можно обойтись funcall и apply. eval работает только в глобальной области видимости, поэтому вы теряете переменные закрытия.

person Sylwester    schedule 04.06.2014