Как мне сделать замыкания в Emacs Lisp?
Я пытаюсь на лету создать функцию, которая бы возвращала одно постоянное значение.
В JavaScript и других современных императивных языках я бы использовал замыкания:
function id(a) {
return function() {return a;};
}
но Emacs lisp не поддерживает их.
Я могу создать сочетание функции идентификации и частичной функции приложения, но она также не поддерживается.
Так как мне это сделать?
7 ответов
Глупая идея
(defun foo (x)
`(lambda () ,x))
(funcall (foo 10)) ;; => 10
Нашел другое решение с лексической-лет
(defun foo (n)
(lexical-let ((n n)) #'(lambda() n)))
(funcall (foo 10)) ;; => 10
Реальные (не поддельные) замыкания в Emacs 24.
Хотя Emacs 24 имеет лексическую черпаемость, когда переменная лексическая привязка имеет значение t, специальная форма defun не работает должным образом в контекстах с лексической привязкой (по крайней мере, в Emacs 24.2.1.) Это затрудняет, но не делает невозможным определить реальные (не поддельные) замыкания. Например:
(let ((counter 0))
(defun counting ()
(setq counter (1+ counter))))
не будет работать должным образом, потому что счетчик символов в defun будет связан с глобальной переменной этого имени, если она есть, а не с лексической переменной, определенной в let. Когда вызывается функция подсчета, если глобальная переменная не существует, она, очевидно, потерпит неудачу. Однако, если есть такая глобальная переменная, она будет обновлена, что, вероятно, не то, что предполагалось, и может быть трудно отследить ошибку, так как может показаться, что функция работает должным образом.
Байтовый компилятор выдает предупреждение, если вы используете defun таким образом, и, вероятно, проблема будет решена в какой-то будущей версии Emacs, но до тех пор можно использовать следующий макрос:
(defmacro defun** (name args &rest body)
"Define NAME as a function in a lexically bound context.
Like normal `defun', except that it works correctly in lexically
bound contexts.
\(fn NAME ARGLIST [DOCSTRING] BODY...)"
(let ((bound-as-var (boundp `,name)))
(when (fboundp `,name)
(message "Redefining function/macro: %s" `,name))
(append
`(progn
(defvar ,name nil)
(fset (quote ,name) (lambda (,@args) ,@body)))
(if bound-as-var
'nil
`((makunbound `,name))))))
Если вы определите счет следующим образом:
(let ((counter 0))
(defun** counting ()
(setq counter (1+ counter))))
он будет работать, как ожидалось, и обновлять счетчик лексически связанных переменных каждый раз, когда он вызывается, возвращая новое значение.
CAVEAT: макрос не будет работать должным образом, если вы попытаетесь определить функцию ** с тем же именем, что и у одной из лексически связанных переменных. Т.е. если вы делаете что-то вроде:
(let ((dont-do-this 10))
(defun** dont-do-this ()
.........
.........))
Я не могу представить, чтобы кто-то на самом деле делал это, но это стоило упомянуть.
Примечание: я назвал макрос defun**, чтобы он не конфликтовал с макросом defun * в пакете cl, однако он никак не зависит от этого пакета.
Emacs lisp имеет только динамическую область видимости. Есть lexical-let
макрос, который аппроксимирует лексическую область видимости через довольно ужасный хак.
Я не уверен в Emacs Lisp, но, насколько я знаю, большое отличие от Common Lisp заключается в том, что он использует динамическую область видимости. Руководство Emacs Lisp утверждает, что Emacs Lisp не имеет замыканий.
Я постараюсь применить свои теоретические знания в области динамического определения объема работ.
Если у вас есть функция id
который просто возвращает значение my-id
:
(defun id () мой ID)
и вы используете его в какой-то другой функции:
(defun some-other-place () (Я бы))
и где-то на пути к вызову id
ты связываешь my-id
например, через let:
(определение даже в другом месте () (пусть ((my-id 5)) (Некоторые-другое место)))
это должно вернуть 5.
Я знаю, что динамический обзор - странный зверь, когда вы привыкли к лексическому анализу, но, возможно, вы можете использовать это для реализации желаемого поведения.
Emacs 24 имеет лексическую привязку.
;; -*- lexical-binding:t -*-
(defun create-counter ()
(let ((c 0))
(lambda ()
(setq c (+ c 1))
c)))
(setq counter (create-counter))
(funcall counter) ; => 1
(funcall counter) ; => 2
(funcall counter) ; => 3 ...