【问题标题】:Nested `defun` produces a repeated warning in Allegro Common Lisp嵌套的“defun”在 Allegro Common Lisp 中产生重复警告
【发布时间】:2014-04-18 20:22:13
【问题描述】:

我在 Common Lisp 中有一个通用的合并排序实现:我有不同的拆分和合并函数实现,并且对于拆分和合并函数的每个组合,我想构造一个合并排序函数。

  • 任何拆分函数都将字符串列表作为输入并返回包含两个列表的列表:原始列表的两半。
  • 任何合并函数都将两个排序列表作为输入并返回排序后的合并列表。

每个归并排序函数都是通过调用以下函数来创建的:

(defun make-merge-sort-function (split-function merge-function)

  (defun merge-sort (lst)
    (if (or (null lst) (null (cdr lst)))
        lst
        (let ((s (funcall split-function lst)))
          (funcall merge-function (merge-sort (car s)) (merge-sort (cadr s))))))

  (lambda (lst)
    (merge-sort lst)))

我在make-merge-sort-function 中定义了merge-sort,以保持其名称的私密性并避免破坏名称空间。

我的代码适用于多个 Common Lisp 实现(例如 Steel Bank Common Lisp):

  • 我的所有测试运行的输出都已正确排序,
  • 每个生成的合并排序函数都有不同的执行时间,表明使用了不同的拆分/合并组合。

所以,我假设我的代码是正确的。

但是,如果我使用 Allegro Common Lisp 运行程序,我会收到警告:

Warning: MERGE-SORT is defined more than once as `operator' in file foo.lisp.

其中foo.lisp 是调用make-merge-sort-function 的文件。因此,该程序运行良好,但在第一次调用make-merge-sort-function 之后,它会打印一次此警告。 如果我将merge-sort 函数设为全局(带有两个附加参数split-functionmerge-function),那么警告就会消失。

我没有在 Allegro Common Lisp 中找到有关此警告含义的任何指示。我尝试过的其他实现(ABCL、CMUCL、CCL、CLISP、SBCL)不会发出任何警告。我认为可以多次定义内部函数(闭包)的新实例,但我不明白为什么这应该是一个问题。 有什么想法吗?

【问题讨论】:

    标签: nested common-lisp mergesort definition allegro-cl


    【解决方案1】:

    从不在 Common Lisp 中嵌套 defun

    就像你在里面使用let defun 而不是 defvar,你用 labels 改为 嵌套defun:

    (defun make-merge-sort-function (split-function merge-function)
      (labels ((merge-sort (lst)
                 (if (or (null lst) (null (cdr lst)))
                     lst
                     (let ((s (funcall split-function lst)))
                       (funcall merge-function (merge-sort (car s)) 
                                               (merge-sort (cadr s)))))))
        #'merge-sort))
    

    【讨论】:

    • 我不知道flet,非常感谢!我认为这是唯一的解决方案:普通的 lambda 行不通,因为我需要递归。
    • 我仍然不明白为什么不应该使用嵌套的defun(并且似乎可以正常工作)。
    • CL 提供了很多机会让自己一脚射门。它是一种复杂的语言,与 Scheme 大不相同。
    • 我误解了嵌套defun 的语义。我想要的是一个嵌套函数,就像 Scheme 的嵌套 define。相反,我只是在函数体中多次执行defun,这导致在当前包中多次定义函数。我什至怀疑这是否有效:即使所有测试都通过了,因为输出列表已排序,我认为所有定义的函数都使用相同的算法,即最后一个已定义的全局 merge-sort。所以,这个问题值得问,因为我从答案中学到了很多东西。
    【解决方案2】:

    您没有显示您的测试代码,因此,请考虑以下 CLISP 会话记录:

    [3]> (defun make-merge-sort-function (split-function merge-function)
      (defun merge-sort (lst)
        (if (or (null lst) (null (cdr lst)))
            lst
            (let ((s (funcall split-function lst)))
              (funcall merge-function (merge-sort (car s))
                                      (merge-sort (cadr s))))))
      (lambda (lst)
        (merge-sort lst)))
    MAKE-MERGE-SORT-FUNCTION
    [4]> (fboundp 'merge-sort)
    NIL                         <<---------------------------- NB
    [5]> (defun sp1(x)(princ" IN SPL1 ") x)
    SP1
    [6]> (defun sp2(x)(princ" IN SPL2 ") x)
    SP2
    [7]> (defun mg1(x y)(princ" IN MRG1 ") x)
    MG1
    [9]> (setq f1 (make-merge-sort-function #'sp1 #'mg1))
    #<FUNCTION :LAMBDA (LST) (MERGE-SORT LST)>
    [10]> (fboundp 'merge-sort)
    T                           <<---------------------------- NB !!
    [12]> (funcall f1 '(1 2 3))
     IN SPL1                    <<---------------------------- NB
    *** - CDR: 1 is not a list
    [14]> (setq f2 (make-merge-sort-function #'sp2 #'mg1))
    #<FUNCTION :LAMBDA (LST) (MERGE-SORT LST)>
    [15]> (funcall f1 '(1 2 3))
     IN SPL2                    <<---------------------------- NB !!!
    *** - CDR: 1 is not a list
    

    现在您可以看到您的代码所做的事情与您认为的有所不同,而您的测试代码可能只是没有解决这个问题。

    显然,嵌套的defun 定义了一个全局可访问的函数merge-sort,第二次调用make-merge-sort-function 重新定义了它。

    【讨论】:

    • 非常感谢您提供非常有用的答案 (+1)。所以defun 总是在当前命名空间中定义一个全局函数。在另一个函数中执行它不会改变这一点。我使用了与 Scheme 的嵌套 defines 相同的语义(我不是 Scheme 或 Common Lisp 方面的专家)。我真正想要的是一个本地函数,fletlabels(用于递归函数)是正确的解决方案。您的示例还解释了我在一些旧代码中一直收到的另一个警告(由嵌套的defun 引起)。我现在已经修好了。
    • 很高兴能帮上忙。并为提出恰当的问题 +1 并保持参与度。
    猜你喜欢
    • 2017-02-27
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 2018-02-21
    • 2021-12-25
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    相关资源
    最近更新 更多