如何在 lisp 中生成加法器函数?

How to generate adder function in lisp?

我需要创建一个生成如下函数的函数:

(defun add3 (val) (+ 3 val))

应该可以这样使用:

(setq adder7 nil)
(fset 'adder7 (make-adder 7))
(adder7 3)
     => 10

理想情况下,函数 make-adder 应该 return 除了参数之外没有任何符号的 lambda,例如:

(make-adder 7)
      => (lambda (val) (+ 7 val))

更新

我尝试过以下天真的实现:

(defun make-adder (n)
  (lambda (x) (+ n x)))

但这会生成一个包含自由符号(不是数字常量!)的 lambdda,并且它的使用失败。

(defalias 'add1 (make-adder 1))
(add1 2)
       => Debugger error void-variable n

(let ((n 5))
  (add1 2))
       => 7

这根本不是我想要的。

Emacs

Emacs 默认依赖 dynamic scoping。这就是返回的 lambda 中的 n 符号引用未绑定变量的原因。要么切换词法作用域,要么构建一个具有当前值 n 的 lambda 形式,如下所示:

(defun make-adder (n)
  `(lambda (x) (+ ,n x)))

然后:

(defalias  'add1 (make-adder 1))
(add1 3)
=> 4 

普通 Lisp

我原本以为这个问题是关于 Common Lisp 的(fset 应该给了我一个提示),你只需要这样做:

(defun make-adder (n)
  (lambda (x) (+ n x)))

你的函数接受一个 n 和 returns 一个匿名函数,它接受另一个参数 x 并产生结果。 但是等等,make-adder 只是 partially applying some arguments to a function (see this question 的一个特例,详细说明了柯里化和部分应用之间的区别)。部分应用函数的一般方法是:

(defun partial (function &rest partial-args)
  (lambda (&rest args)
    (apply function (append partial-args args))))

例如:

(let ((3+ (partial #'+ 3)))
  (funcall 3+ 7))
=> 10

解决了正确的问题:emacs 的动态作用域意味着当调用 adder 函数时,您无法再访问范围内的 n 值.不过,我认为使用 emacs 的 lexical-let 可能更具描述性,而不是使用反引号来构造带有常量注入的函数,因为您正在寻找的是词法关闭。需要明确的是,这是您现在获得的行为,具有动态范围:

(defun dyn-adder (n)
  (lambda (x)
    (+ n x)))

(funcall (dyn-adder 3) 5)
;; (void-variable n) error...

下面是如何使用 lexical-let 获得实际的词法闭包:

(defun lex-adder (n)
  (lexical-let ((n n))
    (lambda (x)
      (+ n x))))

(funcall (adder 3) 5)
;; 8

在定义一个简单的加法器时,反引号解决方案和 lexical-let 之间没有太大区别,但在某些情况下,实际拥有一个您正在引用的变量很重要。例如,如果你想创建一个 accumulator,你需要那个本地状态。使用动态范围,您会得到相同的 void-variable 错误:

(defun dyn-accumulator (n)
  (lambda (x)
    (incf n x)))

(let ((acc (dyn-accumulator 5)))
  (funcall acc 3)
  (funcall acc 8))
;; (void-variable n) error...

使用反引号方法,您会得到不同的错误。 (不过,我不确定我是否正确地执行了此操作。我认为我得到的错误是由于尝试对列表进行 funcall,而不是来自其中包含常量的函数。但无论如何,它应该是明确 (lambda (x) (incf 5 x)) 将不起作用,因为您无法增加常数值。)

(defun bq-accumulator (n)
  `(lambda (x)
     (incf ,n x)))

(let ((acc (bq-accumulator 5)))
  (funcall acc 3)
  (funcall acc 8))
;; wrong type argument error...

但是使用lexical-let,我们有一个可以修改的真实变量:

(defun lex-accumulator (n)
  (lexical-let ((n n))
    (lambda (x)
      (incf n x))))

(let ((acc (lex-accumulator 5)))
  (funcall acc 3)
  (funcall acc 8))
;; 16

我在为 Emacs lisp: why does this sexp cause an invalid-function error? 写的答案中了解并描述了更多 lexical-let;这也可能是有用的信息。