如何在 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;这也可能是有用的信息。
我需要创建一个生成如下函数的函数:
(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
(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;这也可能是有用的信息。