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

San*_*anu 2 lisp common-lisp

我正在学习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在作为函数参数传递时停止此评估?

Kaz*_*Kaz 6

你不能写这个功能; 它必须是一个宏运算符.如果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)


Rai*_*wig 5

创建函数,编译它并将其存储在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)