以编程方式构建具有defun的Common Lisp函数

时间:2017-05-08 23:56:51

标签: function compilation common-lisp eval programmatically-created

我想使用defun定义一个函数,但不是顶级函数。 (函数名称和主体需要根据一些用户输入构建。)以下说明了基本目标:

(let ((name 'fn1)
      (body "abc"))
  (eval `(defun ,name ()
           ,body))
  (push name *function-names*))

1)这有效,但如果没有eval怎么办?

2)目前,我正在用

编译一些这样的函数
(loop for fn-name in *function-names* do
      (setf (symbol-function fn-name)
        (compile nil (symbol-function fn-name))))

但有没有更好的方法只使用像`(compile,fn-name)这样的fn-name,当函数体包含对函数的递归调用时,它会避免SBCL编译器警告?

2 个答案:

答案 0 :(得分:3)

在没有DEFUN的情况下进行此操作会使其有点问题。我认为这是ANSI Common Lisp的一部分,并不是那么好。

如果查看Common Lisp的实现,他们会将DEFUN表单扩展为特定于实现的构造。通常我们会看到像这样名为lambda 的东西。

SBCL:

(defun foo ()
  (foo))

- >

(SB-INT:NAMED-LAMBDA FOO ()
  (BLOCK FOO (FOO)))

评估为:

#<FUNCTION FOO {1003271FFB}>

不幸的是,名为lambda 不是ANSI CL中的概念。

要做到这一点,你需要:

  • 构建DEFUN表格
  • 评估
  • 可选择COMPILE it

为了获得类似的效果,我们可以创建一个文件,编译并加载它。

或者我们可以尝试不使用DEFUN。

  • 我们需要确保全局名称不是宏
  • 然后我们构建一个lambda表达式
  • 然后我们编译那个表达

示例:递归函数:

(compile 'my-fn '(lambda () (my-fn))

现在,在编译lambda时,我们仍然会收到有关未定义函数的警告。我们怎么能提供名字? LABELS会这样做:

(compile 'my-fn
         (lambda ()
           (labels ((my-fn ()
                      (my-fn)))
             (my-fn))))

LABELS也会设置BLOCK,就像DEFUN一样。

当我们现在调用全局MY-FN时,它将调用本地MY-FN,然后可以递归调用自己。

让我们看看是否有更多的替代方法......

  • 也可以使用带有COMPILE的普通LAMBDA并首先声明该功能。这也可能会抑制编译警告。

答案 1 :(得分:2)

我对于“为什么”尝试使用defun执行此操作感到困惑,但如果您希望用户能够定义他们自己的功能,那么致电他们,有几种方法可以解决这个问题。

1)如果您要像普通的defun函数一样进行评估,那么您可以执行类似

的操作
(loop for f in *my-functions-names* and
      for a in *my-functions-args* and
      for b in *my-functions-bodies*
  do (setf (symbol-function f) (compile `(lambda ,a ,b))))

然后用户可以在执行时编译他们的函数,看起来它一直都在那里。

2)创建一个lambdas哈希表,并funcall(或apply)他们的函数:

(defparameter *user-functions* (make-hash-table))

(defun def-user-fun (f-name f-args f-body &optional (ns *user-functions*))
  (setf (gethash f-name ns) (compile nil `(lambda ,f-args ,f-body))))

(defun call-user-fun (f-name args &optional (ns *user-functions*))
  (funcall (gethash f-name *user-funcations) f-args))

这将允许用户定义他们想要的功能,这些功能将在当前环境中进行评估。

3)如果这只是为了节省编译自己代码的时间,那么你可以用defmacro和循环语句来做你所做的事。

<强>更新

由于您需要递归,因此您可以执行类似于@rainerjoswig对(lambda (args..) (labels ((recurse ...)) (recurse ...)))建议的操作。尽管在命令式方面看起来似乎并不“好”,但这在Lisp中是惯用的。实际上,SBCL compilergarbage collector都专门针对这种递归进行了调整。如果您想了解可以依赖的Common Lisp优化,您应该阅读standard

(compile 'fname) == (setf (symbol-function 'fname) (compile nil (symbol-function 'fname)))。至少可能不是你想的那样。在我看来,你对两件事感到困惑,这两件事都是lisp“后端”的神秘机器的一部分。

首先,symbol-function不是评估者,它只是让lisp读者知道符号是函数符号并且应该通过 function 环境而不是当前的词法环境访问,也就是说,一个函数像变量一样被访问,但是在不同的环境或命名空间中。我会在后台下注labels如何扩展这个函数。

其次,compile也不起作用。你可以传递一个symbol-function名称,它访问存储的函数定义,编译它,然后使用编译定义(compile 'my-func)的旧定义,你可以传递它它将编译的lambda表达式(compile nil (lambda (args...) body)),或者,最后,您可以传递一个名称和一个定义,它将编译的函数存储为该函数变量。

所以是的,从某种意义上说,它确实会扩展为setf但不会扩展到您的特定setf,因为您不应该(compile nil (symbol-function 'fname))

至于允许递归,通过解构一般defun形式的

形式来扩展@rainerjoswig是很简单的
(def-user-func count-down (val) 
  (if (> val 0)
    (progn (print val) (count-down (1- val)))
    nil))

使用像

这样的宏
(defmacro def-user-func (name args &body body)
  `(compile ',name (lambda (,@args)
                     (labels ((,name (,@args)
                                ,@body))
                        (,name ,@args)))))

由于lisp阴影变量在letlabels以及其他类似语句中的命名方式,这将非常有效。 “外部”世界只看到一个函数,而函数只看到它自己的labels函数。当,@body展开时,它会在labels的上下文中展开,因为我们复制了名称,所以它会正常工作。