В настоящее время я пытаюсь научиться программировать на "Common Lisp". Поэтому я сейчас занимаюсь небольшой проблемой, касающейся определения макросов. Упомянутый макрос должен работать следующим образом: он берет список чисел, создает временный (изначально пустой) список, который заполняется числами в квадрате и возвращает его. К сожалению, у меня есть некоторые проблемы с этим исходным кодом.
(defmacro square_loop (args)
(let ((res ()))
(loop for x in args
do (push (* x x) res))
res
)
)
Что касается сообщений об ошибках, я думаю, что ошибка заключается в том, что я возвращаю не список, а вызов функции, который имеет то же содержимое, что и список. Однако это будет расцениваться как вызов функции, что неверно. Есть ли способ, как я могу написать макрос правильно?
РЕДАКТИРОВАТЬ
Я знаю, что технически могу решить эту проблему, используя такие функции, как mapcar. Тем не менее, я хотел посмотреть, возможна ли вообще моя идея макроса.
Я думаю, вы не понимаете, что такое макросы и каков их вариант использования. Я предлагаю вам прочитать такие материалы, как
7. Макросы: стандартные конструкции управления
8. Макросы: определяем свои собственные
2.4.6 Обратная цитата + запятая (операторы, используемые в моем решении)
Обычно вы используете макросы для преобразования исходного кода или ситуаций, когда вы хотите контролировать, когда (и если когда-либо) что-то оценивается. Вам следует избегать макросов, если ваша проблема может быть решена с помощью функции. И я не вижу смысла использовать макрос для возведения в квадрат каждого числа в списке.
Но только для обучения...
(defmacro square-loop (args)
(let ((res '()))
(loop for x in args
do (push (* x x) res))
`',res))
> (square-loop (1 2 3))
(9 4 1)
Обратите внимание на три вещи:
push
помещает каждый элемент в начало списка, поэтому конечный результат фактически инвертируется — я не уверен, хотите ли вы этого, поэтому вот аналогичное решение без реверсирования:(defmacro square-loop (args)
`',(mapcar (lambda (x) (* x x)) args))
> (square-loop (1 2 3))
(1 4 9)
Спасибо за подробный ответ. Короткий дополнительный вопрос: Итак, если макросы используются для расширения языка, определение DSL было бы подходящей ситуацией, когда я мог бы использовать макросы, верно?
@IlikedCPlusPlus: да, макросы для этого и нужны: беспрепятственно расширять язык, который у вас есть, на язык, который вы хотите.
CL-USER 1 > (defmacro square_loop (args)
(let ((res ()))
(loop for x in args
do (push (* x x) res))
res))
SQUARE_LOOP
CL-USER 2 > (square_loop (list 1 2 3))
Error: In * of (LIST LIST) arguments should be of type NUMBER.
1 (continue) Return a value to use.
2 Supply new arguments to use.
3 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 3 : 1 >
Ваше главное непонимание заключается в том, что должны делать макросы: они берут код и генерируют новый код. Ваш макрос не генерирует никакого кода. Он вычисляет результат во время раскрытия макроса.
Итак, есть один случай, когда он может сделать что-то полезное:
CL-USER 6 > (square_loop (1 2 3 4))
Error: Illegal car 16 in compound form (16 9 4 1).
1 (continue) Evaluate 16 and ignore the rest.
2 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 7 : 1 >
Так что даже это не работает, так как макрос возвращает (16 9 4 1)
в качестве результата -> в виде кода! Но (16 9 4 1)
недопустимый код Lisp. Помните, что корректный код на Лиспе предполагает наличие оператора (функции, макроса, специальной формы или лямбда-выражения) в качестве первого элемента формы на Лиспе. Но 16
не допустимый оператор, это число:
CL-USER 8 > (16 9 4 1)
Error: Illegal car 16 in compound form (16 9 4 1).
1 (continue) Evaluate 16 and ignore the rest.
2 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 9 : 1 >
Если вы хотите вернуть данные, вам нужно будет указать их:
'(16 9 4 1)
Мы можем изменить макрос:
CL-USER 10 > (defmacro square_loop (args)
(let ((res ()))
(loop for x in args
do (push (* x x) res))
(list 'quote res)))
SQUARE_LOOP
CL-USER 11 > (square_loop (1 2 3 4))
(16 9 4 1)
Выше берет буквальный список и создает новую форму: (quote (16 9 4 1))
. Затем это выражение в кавычках оценивается как (16 9 4 1)
.
Тем не менее: (square_loop (list 1 2 3 4))
не сработает...
И:
(let ((list '(1 2 3 4)))
(square_loop list))
Выше тоже не получится. Почему? Это бы тебе узнать...
Макросы не для этого. Если вам нужна функция, используйте функцию. Макрос — это то, что преобразует исходный код в другой исходный код и используется для расширения языка.