我正在学习Lisp.现在我正在尝试创建一个函数,它将一些有效的Lisp形式作为参数,并返回一个在调用时执行Lisp形式的函数.例如:
(defun fn (name action)
(setf (symbol-function name)
#'(lambda () action)))
Run Code Online (Sandbox Code Playgroud)
当我传递时,说(+ 4 5 6)该函数是使用特定名称创建的,并在调用时返回总和.
(fn 'add (+ 4 5 6))
(add) ==> 15
Run Code Online (Sandbox Code Playgroud)
但是,如果我调用(fn 'error (assert (= 2 3))它会抛出错误,(= 2 3) must evaluate to a non-NIL value.并且error不会创建带有名称的函数.
如何assert在作为函数参数传递时停止此评估?
你不能写这个功能; 它必须是一个宏运算符.如果fn是函数,那么调用:
(fn 'add (+ 4 5 6))
Run Code Online (Sandbox Code Playgroud)
计算参数(+ 4 5 6),将其减少到值15.函数接收15,而不是表达式.我们可以通过引用代码来"修复"这个问题:
(fn 'add '(+ 4 5 6))
Run Code Online (Sandbox Code Playgroud)
但是我们遇到的问题是代码不会与词法环境相互作用.例如,这不起作用,因为x内部不可见fn:
(let ((x 40)) (fn 'add '(+ x 2)))
Run Code Online (Sandbox Code Playgroud)
要创建一个在适当的环境中计算(+ x 2)的函数,我们必须lambda在同一个词法范围内运算符:
(let ((x 40)) (lambda () (+ x 2)))
Run Code Online (Sandbox Code Playgroud)
您的fn运算符可以编写为生成lambda(没有任何名称)的语法糖:
(defmacro fn (expr) `(lambda () ,expr))
Run Code Online (Sandbox Code Playgroud)
现在我们可以写:
(let ((x 40)) (fn (+ x 2))) ;; returns a function which returns 42
Run Code Online (Sandbox Code Playgroud)
做命名的事情:
(defmacro fn (name expr) `(setf (symbol-function ',name) (lambda () ,expr)))
Run Code Online (Sandbox Code Playgroud)
然而,这是一个相当糟糕的想法; 我们在一个函数中引入了令人讨厌的全局副作用.更好的"命名fn"可能是在某些形式上为函数引入词法绑定的一个.也就是说,它可以像这样使用:
(fn (foo (+ x 2)) (foo))
;; ^^^^^^ foo is a lexical function in this scope
;; denoting the function (lambda () (+ x 2))
Run Code Online (Sandbox Code Playgroud)
这可以这样做:
(defmacro fn ((name expr) &rest forms)
`(flet ((,name () ,expr)) ,@forms)))
Run Code Online (Sandbox Code Playgroud)
或者,如果您希望将名称作为变量绑定而不是函数绑定,那么使用的是 (fn (foo (+ x 2)) (funcall foo)):
(defmacro fn ((name expr) &rest forms)
`(let ((,name (lambda () ,expr))) ,@forms))
Run Code Online (Sandbox Code Playgroud)
创建函数,编译它并将其存储在name:
(defun fn (name action)
(compile name
`(lambda () ,action)))
Run Code Online (Sandbox Code Playgroud)
让我们试试看:
CL-USER 13 > (fn 'add '(+ 4 5 6))
ADD
NIL
NIL
CL-USER 14 > (add)
15
Run Code Online (Sandbox Code Playgroud)