Как сделать закрытие в Emacs Lisp?

Я пытаюсь создать функцию "на лету", которая вернет одно постоянное значение.

В JavaScript и других современных императивных языках я бы использовал закрытие:

function id(a) {
    return function() {return a;};
}

но Emacs lisp не поддерживает их.

Я могу создать сочетание функции идентификации и приложения с частичной функцией, но она также не поддерживается.

Итак, как мне это сделать?

Ответ 1

Глупая идея: как насчет:

(defun foo (x)
  `(lambda () ,x))

(funcall (foo 10))  ;; => 10

Ответ 2

Нашел другое решение с лексико-let

(defun foo (n) 
    (lexical-let ((n n)) #'(lambda() n)))

(funcall (foo 10)) ;; => 10

Ответ 3

Реальные (не поддельные) затворы в Emacs 24.

Хотя Emacs 24 имеет лексическое scooping, когда переменная lexical-binding имеет значение t, специальная форма defun не работает должным образом в лексически связанной контексты (по крайней мере, не в Emacs 24.2.1.) Это затрудняет, но не невозможно, определение реальных (не поддельных) закрытий. Например:

(let ((counter 0))
   (defun counting ()
    (setq counter (1+ counter))))

не будет работать так, как ожидалось, потому что символ счетчик в defun будет привязан к глобальной переменной этого имени, если он есть, а не лексическая переменная определите в let. Когда вызывается функция подсчет, если глобальная переменная doesnt существует, она, очевидно, терпит неудачу. Если есть такая глобальная переменная, она обновляется, что, вероятно, не является тем, что было предназначено, и может быть трудно отследить ошибку, так как функция может работать правильно.

Байт-компилятор дает предупреждение, если вы используете 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))))

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

CAVEAT: Макрос не будет работать должным образом, если вы попробуете defun ** функцию с тем же именем, что и одна из лексически связанных переменных. Если вы делаете что-то вроде:

(let ((dont-do-this 10))
  (defun** dont-do-this ()
    .........
    .........))

Я не могу представить, что кто-то на самом деле это делает, но стоит упомянуть.

Примечание. Я назвал макрос defun **, чтобы он не столкнулся с макросом defun * в пакете cl, однако это никак не зависит от этого пакета.

Ответ 4

Emacs lisp имеет только динамическое масштабирование. Там макрос lexical-let, который аппроксимирует лексику, просматривая довольно страшный хак.

Ответ 5

Я не уверен в Emacs Lisp, но, насколько я знаю, большая разница с Common Lisp заключается в том, что он использует динамическое масштабирование во всем. Emacs Lisp Manual утверждает, что Emacs Lisp не имеет замыканий.

Я попытаюсь применить свои теоретические знания о динамическом охвате.

Если у вас есть функция id, которая возвращает значение my-id:

(defun id ()
  my-id)

и вы используете его в некоторой другой функции:

(defun some-other-place ()
  (id))

и где-то на пути к вызову id вы связываете my-id через, например. a let:

(defun even-elsewhere ()
  (let ((my-id 5))
    (some-other-place)))

это должно вернуть 5.

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

Ответ 8

;; -*- 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 ...