Lisp-打印输出()而不是空列表的nil

Lisp-打印输出()而不是空列表的nil,lisp,common-lisp,Lisp,Common Lisp,我有一个Lisp程序,它遍历嵌套列表并删除与传递给函数的元素相匹配的元素。我的问题是,如果其中一个嵌套列表中的所有内容都被删除,我需要打印()而不是NIL (defun del (x l &optional l0) (cond ((null l) (reverse l0)) ((if (atom x) (eq x (car l)) (remove (car l) x)) (del x (cdr l) l0)) (T (del x (cdr l) (cons (if (

我有一个Lisp程序,它遍历嵌套列表并删除与传递给函数的元素相匹配的元素。我的问题是,如果其中一个嵌套列表中的所有内容都被删除,我需要打印()而不是NIL

(defun del (x l &optional l0)
  (cond ((null l) (reverse l0))
    ((if (atom x) (eq x (car l)) (remove (car l) x)) (del x (cdr l) l0))
    (T (del x (cdr l) (cons (if (not (atom (car l))) 
                                    (del x (car l)) 
                                    (car l))
                                 l0)))))

(defun _delete(a l)
(format t "~a~%" (del a l)))

(_delete 'nest '(nest (second nest level) (third (nest) level)))
这是回报

((SECOND LEVEL (THIRD NIL LEVEL))
我需要

((SECOND LEVEL (THIRD () LEVEL))

我尝试过使用~:S格式,但显然对复合结构不起作用。我还尝试了替换函数来替换NIL,也没有结果

两种可能的解决方案:

I.您可以使用格式指令
~:A
~:S

(format t "~:a" '()) => ()
但是,该指令仅适用于列表的顶级元素,即

(format t "~:a" '(a b () c))
不会打印
(A B()C)

但是
(A B NIL C)

因此,如果元素是cons,则需要循环遍历列表,将
~:A
递归地应用于每个元素

(defun print-parentheses (l)
  (cond ((consp l) (format t "(")
              (do ((x l (cdr x)))
                  ((null x) (format t ")" ))
                (print-parentheses (car x))
                (when (cdr x) (format t " "))))
        (t (format t "~:a" l)) ))


(print-parentheses '(a b (c () d))) => (A B (C () D))
二,。为空列表创建打印派送函数,并将其添加到“打印派送表”中:

(defun print-null (stream obj)
  (format stream "()") )

(set-pprint-dispatch 'null #'print-null)

(print '(a () b)) => (A () B) 

后者更简单,但它会影响所有环境,这可能不是您想要的。

对于要打印的对象为
NIL
的情况,我们可以为
打印对象编写一个
:around
方法

(defvar *PRINT-NIL-AS-PARENS* nil
  "When T, NIL will print as ().")

(defmethod print-object :around ((object (eql nil)) stream)
  (if *print-nil-as-parens*
      (write-string "()" stream)
    (call-next-method)))

(defun write-with-nil-as-parens (list)
  (let ((*print-nil-as-parens* t))
    (write list)))
例如:

CL-USER 73 > (write-with-nil-as-parens '(a b c nil (()) (nil)))
(A B C () (()) (()))                  ; <- printed
(A B C NIL (NIL) (NIL))               ; <- return value
CL-USER 73>(以nil作为参数写入(abc nil(())(nil)))
(A、B、C()(())(());
我还尝试了替换函数来替换NIL,也没有结果

所有标准替换函数都不起作用
substitute
是一个序列处理函数:它不会递归到树结构中

sublis
subst
函数将处理树状结构,但它们平等地对待conses的
car
cdr
字段:如果我们用
替换整个树状结构中的
nil
:无论什么
,这适用于所有终止原子,因此
(a nil b)
变为
(a:whatever b:whatever)

我们必须制定类似于
subst
的功能,但只影响
汽车
-s:

(defun subcar (old new nested-list)
  (cond
    ((eq nested-list old) new)
    ((atom nested-list) nested-list)
    (t (mapcar (lambda (atom-or-sublist)
                 (subcar old new atom-or-sublist))
               nested-list))))
有了它,我们可以用字符串
“()”
)替换
nil
-s:

如果我们很好地打印它,字符串只是作为数据打印,而不是作为机器可读的字符串文本打印:

[2]> (format t "~a~%" *)  ;; * in the REPL refers to result of previous evaluation
(A B C () (E () F (G ())) ())

我希望你能理解
nil
()
的意思完全相同;它们是相同的对象:

[3]> (eq nil ())
T
符号标记
nil
可以表示除
()
之外的对象的唯一方法是,如果我们在一个包中,而该包没有从
common lisp
包导入
nil
符号(并且
nil
作为本地符号插入该包中,与
cl:nil
完全无关):

现在让我们看看这个包中的
nil
是否是
()

哎呀!这不再是标准的
nil
;它没有特殊的行为,它对自己进行评估。我们必须引用它:

FOO[6]> (cl:eq 'nil ())
COMMON-LISP:NIL

不,不是
()
对象。注意
cl:eq
函数的返回值如何打印为
COMMON-LISP:NIL
COMMON-LISP:T
。只有当符号出现在当前包中时,才会在不带包前缀的情况下打印符号。

要使pprint分派生效,
*print pretty*
需要为T。在这种情况下,我倾向于专门处理类
null
。我想这只是风格上的差异,但是专门研究
(eql nil)
有什么技术原因吗?可移植性?@coredump:只是风格而已。甚至没有比较过性能。我倾向于将NIL视为一个对象,而不是一个类的实例,它代表一个类型。谢谢,我理解。请参阅的第19条-定义此方法的后果是未定义的。我认为pprint分派是一种方式。@Xach:在这种情况下,我不会太在意编写一个方法,尽管它是一种黑客行为。我要添加的唯一安全检查是,看看该方法是否已经存在。。。pprint调度非常好,但是您正在打印列表,这可能是您想要的,也可能不是您想要的。要摆脱漂亮的打印(修改了分派表)并仍然使用pprint分派是很困难的。例如,我找不到如何创建空的调度表。请问您为什么需要这样做?
[1]> (defpackage "FOO" (:use))
#<PACKAGE FOO>
[2]> (in-package "FOO")
#<PACKAGE FOO>
FOO[3]> (cl:eq cl:nil ())
COMMON-LISP:T
FOO[4]> (cl:eq nil ())

*** - SYSTEM::READ-EVAL-PRINT: variable NIL has no value
FOO[6]> (cl:eq 'nil ())
COMMON-LISP:NIL