我正在学习Lisp。现在我正在尝试创建一个函数,它将一些有效的Lisp形式作为参数,并返回一个在调用时执行Lisp形式的函数。例如:
(defun fn (name action)
(setf (symbol-function name)
#'(lambda () action)))
当我传递说(+ 4 5 6)
时,函数将使用特定名称创建,并在调用时返回总和。
(fn 'add (+ 4 5 6))
(add) ==> 15
但如果我调用(fn 'error (assert (= 2 3))
,则会抛出错误(= 2 3) must evaluate to a non-NIL value.
,并且不会创建名为error
的函数。
当作为函数参数传递时,如何停止对assert
的评估?
答案 0 :(得分:6)
你不能写这个功能;它必须是一个宏运算符。如果fn
是一个函数,则调用:
(fn 'add (+ 4 5 6))
计算参数(+ 4 5 6)
,将其减少到值15.函数接收15,而不是表达式。我们可以修复"这是通过引用代码:
(fn 'add '(+ 4 5 6))
但是我们遇到的问题是代码与词汇环境没有交互。例如,这不会起作用,因为x
中fn
不可见:
(let ((x 40)) (fn 'add '(+ x 2)))
要创建一个在适当的环境中计算(+ x 2)的函数,我们必须在同一个词法范围内使用lambda
运算符:
(let ((x 40)) (lambda () (+ x 2)))
您的fn
运算符可以编写为生成lambda
(没有任何名称)的语法糖:
(defmacro fn (expr) `(lambda () ,expr))
现在我们可以写:
(let ((x 40)) (fn (+ x 2))) ;; returns a function which returns 42
做命名的事情:
(defmacro fn (name expr) `(setf (symbol-function ',name) (lambda () ,expr)))
然而,这是一个相当糟糕的主意;我们在一个函数中引入了令人讨厌的全局副作用。一个更好的"名为fn"可能是在某些形式上为函数引入词法绑定的一个。也就是说,它可以像这样使用:
(fn (foo (+ x 2)) (foo))
;; ^^^^^^ foo is a lexical function in this scope
;; denoting the function (lambda () (+ x 2))
可以这样做:
(defmacro fn ((name expr) &rest forms)
`(flet ((,name () ,expr)) ,@forms)))
或者,如果您希望将名称作为变量绑定而不是函数绑定,那么使用情况为(fn (foo (+ x 2)) (funcall foo)):
(defmacro fn ((name expr) &rest forms)
`(let ((,name (lambda () ,expr))) ,@forms))
答案 1 :(得分:4)
(defun fn (name action) (setf (symbol-function name) #'(lambda () action))) (fn 'add (+ 4 5 6)) (add) ==> 15
这不会以同样的方式处理add
和(+ 4 5 6)
。你引用一个(因为你想要一个符号),而不是另一个,即使你想要一个列表。要获得所需的行为,您需要定义一个宏,以便可以阻止评估并将表单放在函数中,或构造一个强制转换为函数的列表。宏观方法:
(defmacro make-fn (name form)
`(setf (symbol-function ',name) #'(lambda () ,form)))
CL-USER> (make-fn add (+ 4 5 6))
#<FUNCTION (LAMBDA ()) {1002D48D09}>
CL-USER> (add)
15
CL-USER> (make-fn err (assert (= 2 3)))
#<FUNCTION (LAMBDA ()) {1002E11359}>
CL-USER> (err)
; Evaluation aborted on #<SIMPLE-ERROR "The assertion ~S failed." {1002E24951}>.
功能和强制方法:
(defun make-fn2 (name form)
(setf (symbol-function name) (coerce (list 'lambda () form) 'function)))
CL-USER> (make-fn2 'add '(+ 4 5 6))
#<FUNCTION (LAMBDA ()) {1004566CB9}>
CL-USER> (add)
15
CL-USER> (make-fn2 'err '(assert (= 2 3)))
#<FUNCTION (LAMBDA ()) {100298D2F9}>
CL-USER> (err)
; Evaluation aborted on #<SIMPLE-ERROR "The assertion ~S failed." {10029CF441}>.
现在,这些方法可以正常使用,但是Rainer Joswig's answer指出有一个标准函数已经为我们完成了大部分工作:compile。它非常通用,但重要的部分是它需要一个名称和一个功能定义。函数定义可以是lambda表达式,它将被强制转换为函数(如上所述),但也会被编译(因为上面的简单强制可能无法编译函数)并将其存储为名称的函数定义,如果名称是非零的。这意味着编译将完成
的所有工作(setf (symbol-function name) (coerce (list 'lambda () form) 'function))
对你而言,还有编译功能的额外好处。 Rainer的答案显示了如何使用它,我认为这是解决这个问题的最优雅的解决方案。
答案 2 :(得分:4)
创建函数,编译它并将其存储在name
:
(defun fn (name action)
(compile name
`(lambda () ,action)))
我们试一试:
CL-USER 13 > (fn 'add '(+ 4 5 6))
ADD
NIL
NIL
CL-USER 14 > (add)
15