LISP从函数返回值的合适方法

时间:2018-08-12 18:15:14

标签: functional-programming common-lisp sbcl

因此,我正在研究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))

2 个答案:

答案 0 :(得分:6)

setq用于更新现有绑定,并且未创建变量retset。标准中未指定处理方式,因此您不能依赖与之接触的代码。您可以使用defparameter and defvar来创建全局变量,而可以使用&aux来创建局部变量,letloop可以使用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
  )

也与loop with clause相同:

(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))))

答案 1 :(得分:3)

基于列表的版本稍好:

  • 联盟涉及两个以上列表
  • 不使用LAST
  • 仍然使用会员
  • 不使用APPEND
  • DOLIST返回值RETSET

代码

(defun new-union (&rest lists
                  &aux (retset (list (caar lists)))
                       (rretset retset))
  (dolist (list lists retset)
    (dolist (el list)
      (unless (member el retset)
        (setf (cdr rretset) (list el)
              rretset (cdr rretset))))))