首页 > 解决方案 > LISP 从函数返回值的适当方式

问题描述

因此,我正在研究 Paul Graham 的 Common Lisp 并且有一个问题要求创建一个联合函数,该函数保持被联合化的列表中元素的顺序。为此,我编写了以下函数:

(defun new-union (listA listB)
  (setq retset (list (car listA)))
  (loop for el in (append (cdr listA) listB)
    do (if (not(member el retset))
      (push el (cdr (last retset)))))
  (return-from new-union retset))

这将返回每个列表的唯一元素,同时保持顺序,因此如果我创建并运行:

(setq listA '(a b c a))
(setq listB '(c d e))
(new-union listA listB)

回报是:

(A B C D E)

所以第一件事是我得到一个编译器警告:"undefined variable: RETSET"在这一行:(setq retset (list (car listA)))。另一件事是,上面的方法似乎是一种更“面向对象”的做事方式,而不是带有return-from语句的LISP方式。

是否可以在没有编译器错误的情况下以更“适合lisp”的方式编写此代码?

*编辑:使用@Sylwester的答案,我重写了如下函数并且没有错误:

(defun new-union (listA listB)
 (let ((retset (list (car listA))))
   (loop for el in (append (cdr listA) listB)
         do (if (not (member el retset))
              (push el (cdr (last retset)))))
   retset))

标签: functional-programmingcommon-lispsbcl

解决方案


setq是更新一个现有的绑定并且你的变量retset没有被创建。标准中未指定如何处理此问题,因此您不能依赖涉及它的代码。defparameter您可以使用和defvar创建全局变量,而您可以使用&auxin 函数创建局部变量,let并且loop可以使用with. 因此:

(defun new-union (list-a list-b)
  (let ((retset (list (car list-a))))
    ...
    retset
    ))

与使用&aux相同:

(defun new-union (list-a list-b &aux (retset (list (car list-a))))
  ...
  retset
  )

也与带有子句的循环相同:

(defun new-union (list-a list-b)
  (loop :with retset := (list (car list-a))
     ...
     :finally (return retset))) 

关于返回值。在尾部位置,评估的值是返回值。例如。

(if (< 3 4)
    8
    10)

这里8返回。这意味着(return from new-union retset)在您的代码中,位于尾部位置的代码可能只写retset.

现在,如果您有不在尾部位置的代码并且您希望早点返回,您可以执行您在尾部位置所做的事情,它会起作用。

我使用(return retset)的那个从最近的未命名 ( nil) 块return-from返回,而从命名块返回。具有允许您选择它产生的块的名称loop的关键字。named

让 lisper 实现这样一个微不足道的功能,你会得到很多答案。有了你的规格和测试,我会做的:

(defun new-union (&rest lists &aux (hash (make-hash-table :test 'equal)))
  (loop :for list :in lists
        :nconc (loop :for element :in list 
                     :if (gethash element hash t)
                         :collect element
                         :do (setf (gethash element hash) nil))))

推荐阅读