Elisp: расширение макроса во время загрузки с оценкой неопределенной переменной
Лисп-новобранец здесь.
Моя цель состоит в том, чтобы определить макрос, который сделает ключи точечного алиста доступными в качестве переменных для доступа к соответствующим значениям, отсюда и название "let-dotted-alist". Итак, что я хочу, это:
(setq foo '((a . "aa") (b . "bb")))
(let-dotted-alist foo
(message (concat a b)))
==> "aabb"
Вот лучшее, что я мог придумать до сих пор:
(defmacro let-dotted-alist (alist &rest body)
"binds the car of each element of dotted ALIST to the corresponding cdr and makes them available as variables in BODY."
`(let ,(nreverse
(mapcar
(lambda (p) (list (car p) (cdr p)))
(eval alist)))
,@body))
Проблема здесь заключается в eval
, Мне это нужно для того, чтобы иметь возможность передать alist как переменную (foo
), а не литерал, который является основным вариантом использования при определении функций. Чтобы макрос работал с кодом, эта переменная должна быть уже связана. Все еще везде я читал, что использование eval имеет тенденцию указывать на недостаток в коде? Есть ли способ обойти это?
Это было бы несколько академической проблемой, если бы в Emacs 24 не было готового расширения макроса, которое хочет расширить макрос во время загрузки, когда переменная (dotlist
в следующем примере), который должен предоставить alist, все еще void:
(defun concat-my-cdrs (dotlist)
(let-dotted-alist dotlist
(print (concat a b))))
В зависимости от того, как я оцениваю это, я либо получаю "mapcar: Значение символа в качестве переменной void: dotlist" или "Eager macro-extension error: (void-variable dotpoint)". Это имеет смысл, конечно, потому что переменная dotlist действительно пуста во время загрузки.
Теперь, прежде чем я попытаюсь найти обходной путь вдоль (локально) отключения активного расширения макроса, есть ли способ улучшить определение макроса, чтобы избежать eval
вообще?
Заранее спасибо!
2 ответа
Я не верю, что ты можешь избежать eval
, способ постановки проблемы: macroexpand let
-привязки в зависимости от переменной. Единственный способ не использовать eval
было бы отложить оценку переменной до макроэкспандирования, но это противоречит требованию расширения let
привязок. Я думаю, что я заявляю что-то, что для вас очевидно.
Таким образом, вопрос, очевидно, заключается в том, можно ли изменить ваши требования, чтобы устранить необходимость eval
, Что означало бы избегать let
-привязки, или сделать привязки явными в макропараметрах. Вам решать, приемлемо ли это, но мое личное мнение - eval
не так плохо, чтобы убить ваш вариант использования. Я бы сохранил eval
там. (По сути, я недавно сделал то же самое в Clojure, где мне нужно было связать одни и те же локальные символы с разными значениями, эмулируя функторы OCaml. Это отступление, чтобы объяснить, почему я, вероятно, склонен придерживаться того, что вы сделали.)
Я мог не знать о некоторых трюках, связанных с elisp- хотя даже тогда я бы предпочел eval
на какую-то хитрость, с которой я никогда не сталкивался.
Во-первых, я упомяну, что расширение eager-macroexpansion не представляет новой проблемы: та же проблема обнаруживается в более раннем Emacsen, если вы пытаетесь скомпилировать файл с помощью байтов.
Как избежать eval
Вы можете, используя что-то еще, что использует eval
например, cl-progv
:
(defmacro let-dotted-alist (alist &rest body)
(macroexp-let2 nil alist alist
`(cl-progv (mapcar #'car ,alist)
(mapcar #'cdr ,alist)
,@body)))
Но обратите внимание, что семантика немного отличается: переменные могут быть только динамически, а не лексически, потому что список переменных будет известен только во время выполнения.