当作为函数参数传递时,如何停止计算lisp表单?

当作为函数参数传递时,如何停止计算lisp表单?,lisp,common-lisp,Lisp,Common Lisp,我在学Lisp。现在,我尝试创建一个函数,该函数将一些有效的Lisp表单作为参数,并在调用时返回一个执行Lisp表单的函数。例如: (defun fn (name action) (setf (symbol-function name) #'(lambda () action))) 当我传递say(+456)时,函数将使用特定名称创建,调用时返回总和 (fn 'add (+ 4 5 6)) (add) ==> 15 但是如果我调用(fn'error(assert(=

我在学Lisp。现在,我尝试创建一个函数,该函数将一些有效的Lisp表单作为参数,并在调用时返回一个执行Lisp表单的函数。例如:

(defun fn (name action)
  (setf (symbol-function name)
        #'(lambda () action)))
当我传递say
(+456)
时,函数将使用特定名称创建,调用时返回总和

(fn 'add (+ 4 5 6))
(add) ==> 15
但是如果我调用
(fn'error(assert(=23))
它抛出的错误
(=23)必须求值为非NIL值。
并且不会创建名为
error
的函数

当作为函数参数传递时,如何停止对
assert
的计算

这不会以相同的方式处理
add
(+456)
。您可以引用一个(因为您需要一个符号),但不是另一个,即使您想要一个列表。要获得所需的行为,您需要定义一个宏,以防止求值并将表单放入函数中,或者构造一个强制到函数的列表。宏方法:

(defmacro make-fn (name form)
  `(setf (symbol-function ',name) #'(lambda () ,form)))
(defun make-fn2 (name form)
  (setf (symbol-function name) (coerce (list 'lambda () form) 'function)))

对您来说,编译函数也有额外的好处。Rainer的回答说明了如何使用它,我认为这是解决这个问题的最优雅的解决方案。

创建函数,编译它并将其存储在
名称下:

(defun fn (name action)
  (compile name
           `(lambda () ,action)))
让我们试试看:

CL-USER 13 > (fn 'add '(+ 4 5 6))
ADD
NIL
NIL

CL-USER 14 > (add)
15

无法编写此函数;它必须是宏运算符。如果
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)))
为了在适当的环境中创建一个计算(+x2)的函数,我们必须在相同的词法范围内正确使用
lambda
运算符:

(let ((x 40)) (lambda () (+ x 2)))
您的
fn
运算符可以编写为生成
lambda
(无任何名称)的语法糖:

现在我们可以写:

(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)) (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(+x2))(funcall foo)):


感谢Kaz的输入。我现在还有一个问题:什么是“词法函数”以及在哪种情况下我可能需要它们?@Santanu词法(或“本地”)函数是由
flet
labels
引入的(区别在于
labels
让定义的函数体“看到”他们自己和彼此,允许自我和相互递归)。当您想将函数的任务分解为更小的函数时,词法函数很有用,但这些函数需要访问父函数中的局部变量。请考虑一下,Lisp中的词法作用域是否可以用于实现更紧密的封装,例如类似于C中的
static
scope的功能@Santanu,C没有闭包,所以C中的
static
说明符只是它们的替代品。有了闭包,你可以做比
static
说明符更有趣的事情,而且更自然。@Mark但在C中,函数本身可以通过函数指针作为变量使用。这不是clos的等价概念吗ures?我们可以将函数指针作为参数传递给函数,也可以将函数指针作为函数的返回类型。+1我总是忘记,
compile
将设置
name
的定义。这是一个非常好和干净的解决方案。这个解决方案对我来说真的非常新鲜。在搜索网络时,我得到
(setf(symbol-function-name)所以在我的脑海里有一个疑问:
(setf(symbol-function-name)…
(编译名称…
?以及何时使用哪个?@Santanu:这将创建函数,编译并存储它。
setf symbol函数
只存储它。@RainerJoswig:好的,所以我可以假设compile在执行函数时比
setf symbol函数
的性能有所提高。刚才我在Lisp上的
中发现了这一点但是,显式调用compile与调用eval相比,是一种极端的措施,应该以同样的怀疑来看待……调用compile不是一种常规使用的编程技术,它是一种极为罕见的技术。因此,请注意不要不必要地调用它。
Ref:Note
error
是一个常见的Lisp函数,你不应该重新定义它。@PauloMadeira这只是一个理解和实验这些概念的示例代码。我知道,但我觉得这个注释是必要的,至少对后代来说是必要的。如果你重新定义
error
,即使只是为了实验,你也应该期待与常见的Lisp im的其余交互至少可以说,实现是非常不稳定的。你最好用不命名CL函数的符号进行实验。除非你对可能发生的事情感到好奇,这可能是一种说教性的探索。谢谢@PauloMadeira,我明白你的意思了。
(defmacro fn (name expr) `(setf (symbol-function ',name) (lambda () ,expr)))
(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)))
(defmacro fn ((name expr) &rest forms)
  `(let ((,name (lambda () ,expr))) ,@forms))