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)))

Но обратите внимание, что семантика немного отличается: переменные могут быть только динамически, а не лексически, потому что список переменных будет известен только во время выполнения.

Другие вопросы по тегам