Подтвердить что ты не робот

Лексический объем в Emacs: совместимость со старым Emacsen

В Emacs 24 добавлены необязательные лексические привязки для локальных переменных. Я хотел бы использовать эту функциональность в своем модуле, поддерживая совместимость с XEmacs и предыдущими версиями Emacs.

До Emacs 24 самым простым способом получить замыкания была форма lexical-let, определенная в cl-macs, которая эмулировала лексическую область с помощью некоторых умных макросов. Хотя это никогда не было чрезвычайно популярным среди программистов elisp, оно действительно работало, создавая реальные и эффективные закрытия, если вы запомнили их обернуть в lexical-let, как в этом псевдокоде:

(defun foo-open-tag (tag data)
  "Maybe open TAG and return a function that closes it." 
  ... do the work here, initializing state ...
  ;; return a closure that explicitly captures internal state
  (lexical-let ((var1 var1) (var2 var2) ...)
    (lambda ()
      ... use the captured vars without exposing them to the caller ...
      )))

Возникает вопрос: какой лучший способ использовать новые лексические привязки, сохраняя поддержку Emacs 23 и XEmacs? В настоящее время я решил это путем определения макроса, специфичного для пакета, который расширяется до lexical-let или в обычном let в зависимости от того, привязан ли lexical-binding и true:

(defmacro foo-lexlet (&rest letforms)
  (if (and (boundp 'lexical-binding)
           lexical-binding)
      `(let ,@letforms)
    `(lexical-let ,@letforms)))
(put 'foo-lexlet 'lisp-indent-function 1)

... at the end of file, turn on lexical binding if available:
;; Local Variables:
;; lexical-binding: t
;; End:

Это решение работает, но оно неуклюже, потому что новая специальная форма нестандартная, не выделяется должным образом, не может быть переделана под edebug и вообще привлекает внимание к себе. Есть ли лучший способ?


ИЗМЕНИТЬ

Два примера идей для более умных (не обязательно хороших) решений, которые позволяют коду продолжать использовать стандартные формы для создания замыканий:

  • Используйте совет или макрос компилятора, чтобы сделать lexical-let expand до let под lexical-bindings, если lexical-let назначает только символы, которые лексически ограничены. Этот совет будет только временно активироваться во время байтовой компиляции foo.el, так что значение lexical-let остается неизменным для остальных Emacs.

  • Используйте средство макрокоманды/кода для компиляции let символов без префикса до lexical-let под старым Emacsen. Это снова применимо только во время байтовой компиляции foo.el.

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


РЕДАКТИРОВАТЬ 2

Поскольку никто не активировал решение, которое позволило бы модулю продолжать использовать let или lexical-let, не нарушая их для остальных Emacs, я принимаю ответ Stefan, в котором говорится, что указанный макрос является способ сделать это. В дополнение к этому ответ улучшает мой код, используя bound-and-true-p и добавляя элегантное объявление для edebug и lisp -indent.

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

4b9b3361

Ответ 1

Так как lexical-let и lexical-binding let не делают то же самое (более конкретно lexical-let всегда использует лексическую привязку, тогда как let использует либо динамическое связывание, либо лексическое связывание в зависимости от того, был ли var defvar 'd или нет), я думаю, что ваш подход примерно так же хорош, как и он. Вы можете легко сделать шаг Edebug, tho:

(defmacro foo-lexlet (&rest letforms)
  (declare (indent 1) (debug let))
  (if (bound-and-true-p lexical-binding)
      `(let ,@letforms)
    `(lexical-let ,@letforms)))

Если вы не хотите зависеть от declare, вы можете использовать (put 'foo-lexlet 'edebug-form-spec 'let).

Ответ 2

Одним из возможных решений является использование defadvice для соединения lexical-let. Я написал следующий совет, и, похоже, он работает нормально. Это также byte-compile известно.

(defadvice lexical-let (around use-let-if-possible (bindings &rest body) activate)
  (if (and (>= emacs-major-version 24)
           (boundp 'lexical-binding)
           lexical-binding)
      (setq ad-return-value `(let ,bindings . ,body))
    ad-do-it))

Ответ 3

Лексически-пусть выглядит так же, как и в формате arglist, как let, так что о чем-то вроде этого:

(if (older-emacs-p)
  (setf (macro-function 'let) (macro-function 'lexical-let))
  (setf (macro-function 'lexical-let) (macro-function 'let)))

Эта прокладка должна позволять более новым Emacs читать лексико-пустые части более старого кода, а также другим способом (позволить более старым Emacs читать разделы с более новым кодом).

Это общий Lisp. Кто-нибудь хочет перевести это в Emacs?

И вы можете столкнуться с трудностями, если lexical-let/let реализуется как специальная форма (а не макрос).

Кроме того, это может полностью нарушить случай, совместимый с прямой связью, если let определен в старших Emacs. Это? (Я очень мало знаю об Emacs, это не мой избранный редактор). Но случай с обратной совместимостью может быть более важным независимо.