如何作为函数参数传递时停止评估lisp表单?

时间:2014-06-25 21:24:08

标签: lisp common-lisp

我正在学习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的评估?

3 个答案:

答案 0 :(得分:6)

你不能写这个功能;它必须是一个宏运算符。如果fn是一个函数,则调用:

(fn 'add (+ 4 5 6))

计算参数(+ 4 5 6),将其减少到值15.函数接收15,而不是表达式。我们可以修复"这是通过引用代码:

(fn 'add '(+ 4 5 6))

但是我们遇到的问题是代码与词汇环境没有交互。例如,这不会起作用,因为xfn不可见:

(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