范式(form)

任何 Lisp系统都包含一个叫做顶层(toplevel)的交互式前端,你在顶层中输入Lisp表达式,系统打印它们的值。

1
2
3
> 1
1
>

系统会打印它的值,跟着另一个提示符,表示它在等待更多的输入. 在这种情况下,打印出来的值和我们输入的一样. 象1这样的数叫做自身求值的

当我们输入一个需要做些求值工作的表达式时

1
2
> (+ 2 3)
5

在表达式(+ 2 3)中,+叫做操作符,数2和3叫做变元
Lisp中我们把+写 在最前面,后面跟着变元,整个表达式被一对括号围住:(+ 2 3). 因为操作 符在前,这叫做前缀表示法
一开始这样写表达式有点怪,我们想把三个数加起来,用通常的表示法我们要写+两次 2 + 3 + 4,而在Lisp中我们仅需增加一个变元: (+ 2 3 4)

1
2
> (/ (- 7 1) (- 4 2))
3

求值

在Lisp中,+是一个函数,形如(+ 2 3)的表达式是函数调用. 当Lisp对函数调用求 值时,它做这样两步:

  1. 首先变元从左至右被求值. 在此例中,每个变元求值到自身,所以变元的值 分别是2和3.
  2. 变元的值传给以操作符命名的函数. 在此例中,即+函数,它返回5.

如果任何变元本身是函数调用,它们按上述规则求值. 这是对(/ (- 7 1) (- 4 2))求值时发生的情况:

  1. Lisp计算(- 7 1): 7求值为7,1求值为1. 它们被传给函数-,它返回6.
  2. Lisp计算(- 4 2): 4求值为4,2求值为2. 它们被传给函数-,它返回2.
  3. 6和2的值传给函数/,它返回3.

并不是所有的Lisp操作符都是函数,但大多数都是. 而函数总是按这种方式求值 的. 变元从左至右被求值,它们的值被传给函数,函数返回整个表达式的值. 这叫做Common Lisp的求值规则.一个不遵守上述规则的操作符是quote

1
2
> (quote (+ 3 5))
(+ 3 5)

为了方便,Common Lisp定义’作为quote的简记法. 通过在任何表达式前面加上’ 你能获得与调用quote同样的效果:

1
2
> '(+ 3 5)
(+ 3 5)

用简记法比用quote普遍得多. Lisp提供quote作为一种保护表达式以防被求值的手段

1
2
3
4
5
6
;; 从麻烦中解脱出来 如果你输入了一些Lisp不能理解的东西,它会打印一条出错信息并把你带到一个 叫中断循环(break loop)的顶层中去. 中断循环给了有经验的程序员弄清出错原 因的机会,不过一开始你唯一需要知道的事是如何从中断循环中出来. 如何返回 顶层取决于你用的Lisp环境. 在这个假设的环境里,用:abort出来:
> (/ 1 0)
Error: Division by zero.
Options: :abort, :backtrace
>> :abort
>

数据

Lisp提供在其它语言中找得到的数据类型,一是整数,另一种和其它语言一样有的是字符串,整数 和字符串都求值到自身. 另两种我们不常在其它语言中发现的是符号和表. 符号是单词. 通常它们被转换成大写,不管你如何输入

1
2
> 'Artichoke
ARTICHOKE

符号(通常)不求值为自身,因此如果你想引用一个符号,请象上面那样用'引用它. 表表示为被括号包围的零个或多个元素. 元素可以是任何类型,包括表. 你必须引用表,否则Lisp会以为它是函数调用:

1
2
3
4
> '(my 3 "Sons")
(MY 3 "Sons")
> '(the list (a b c) has 3 elements)
(THE LIST (A B C) HAS 3 ELEMENTS)

请注意一个引号保护整个表达式,包括里面的表达式. 你可以调用list来构造表. 因为list是一个函数,它的变元被求值. 这是+调用在 list调用里的例子:

1
2
> (list 'my (+ 2 1) "Sons")
(MY 3 "Sons")

现在我们处于欣赏Lisp最非同寻常特征之一的位置上. Lisp程序表达为表.

Lisp程序表达为表. 如果变元的机动性和优雅性没能说服你Lisp记号是一种有价值的工具,这点应该能使你信服. 这意味着Lisp程序可以生成Lisp代码. Lisp程序员能(而且经常)为自己编写能写程序的程序.
在现阶段理解表达式和表的关系也是很重 要的,而不是被它们弄糊涂. 这就是为何我们使用quote. 如果一个表被引用了, 求值返回它本身; 如果没有被引用,它被认为是代码,求值返回它的值:

1
2
> (list '(+ 2 1) (+ 2 1))
((+ 2 1) 3)

此处第一个变元被引用了,所以生成了一个表. 第二个变元没有被引用,视之为函 数调用,得出一个数字. 在Common Lisp中有两种方法表示空表. 你可用一对不包含任何东西的括号来表 示空表,或用符号nil来表示它. 你用哪种方法表示空表都没有关系,不过它会被 显示成nil:

1
2
3
4
> ()
NIL
> nil
NIL

表的操作

函数cons构造表. 如果它的第二个变元是表,它返回一个新表,新表的第一个元素 就是第一个变元:

1
2
> (cons 'a '(b c d))
(A B C D)

我们可以通过把新元素cons到空表来构造新表. 我们在上一节见到的list函数只不过是一个把几样东西cons到nil上去的方便办法:

1
2
3
4
> (cons 'a (cons 'b nil)) // > (cons 'a 'b)
(A B) // (A . B)
> (list 'a 'b)
(A B)

基本的提取表中元素的函数是car和cdr表的car就是它的第一个元素,而 cdr是第一个元素后面的所有东西:

1
2
3
4
5
6
7
8
9
> (car '(a b c))
A
> (cdr '(a b c))
(B C)
> (car (cdr (cdr '(a b c d))))
C
;; 但是,你可以用third更容易地做同样的事:
> (third '(a b c d))
C

真值

符号t是Common Lisp中表示真的缺省值. 就象nil,t求值到自身. 函数listp返回 真如果它的变元是一个表:

1
2
> (listp '(a b c))
T

一个函数叫做断言如果它的返回值被解释成真或假. Common Lisp的断言的名字通常以p结尾. 假在Common Lisp中用nil(空表)来表示. 如果我们传给listp的变元不是表,它返回nil:

1
2
> (listp 27)
NIL

因为nil扮演两个角色,函数null返回真如果它的变元是空表:

1
2
> (null nil)
T

而函数not返回真如果它的变元是假:

1
2
> (not nil)
T

它们完全做的是同样的事情. 要if是Common Lisp中最简单的条件语句. 它一般接受三个变元:一个测试表达式, 一个then表达式和一个else表达式

1
2
3
4
5
6
7
8
9
10
> (if (listp '(a b c))
(+ 1 2)
(+ 5 6)
)
3
> (if (listp 27)
(+ 1 2)
(+ 5 6)
)
11

就象quote,if是特殊操作符. 它不能用函数来实现,因为函数调用的变元总是要 求值的,而if的特点是只有最后两个变元中的一个被求值. if的最后一个变元是可选的. 如果你省略它,它缺省为nil:

1
2
3
> (if (listp 27) 
(+ 2 3))
NIL

虽然t是真的缺省表示,任何不是nil的东西在逻辑上下文中被认为是真:

1
2
> (if 27 1 2)
1

逻辑操作符and和or就象条件语句. 两者都接受任意数目的变元,但只求值能够确定返回值的数目的变元. 如果所有的变元都是真(不是nil),那么and返回最后变元的值:

1
2
> (and t (+ 1 2))
3

但如果其中一个变元是假,所有它后面的变元都不求值了. or也类似,只要它碰到一个是真的变元就继续求值了. 这两个操作符是宏. 就象特殊操作符,宏可以规避通常的求值规则

函数

你可以用defun来定义新的函数. 它通常接受三个以上变元:一个名字,一列参数, 和组成函数体的一个或多个表达式. 这是我们定义third的一种可能:

1
2
3
4
5
6
> (defun our-third (x)
(car (cdr (cdr x))))
OUR-THIRD

> (our-third '(a b c d))
C
1
2
3
4
5
> (defun sum-greater (x y z)
(> (+ x y) z))
SUM-GREATER
> (sum-greater 1 4 3)
T

Lisp对程序,过程或函数不加区别. 函数做了所有的事情(事实上构成了语言本身 的大部分). 你可以认为你的函数中的一个是主函数,但通常你能在顶层里调用任 何一个函数. 这意味着,当你写程序的时候,你能一小段一小段地测试它们.

递归

我们在上一节中定义的函数还调用了其它函数为自己服务. 比如sum-greater调 用了+和>. 函数可以调用任何函数,包括它本身. 自己调用自己的函数是递归的.

1
2
3
4
5
6
7
8
9
10
11
(defun our-member (obj lst)
(if (null lst)
nil
(if (eql (car lst) obj)
lst
(our-member obj (cdr lst)))))

> (our-member 'b '(a b c))
(B C)
> (our-member 'z '(a b c))
NIL

输入和输出

到目前为止,我们一直在利用顶层暗中使用i/o. 对实际的交互式的程序,这可能还不够.
我们看一些输入输出函数. Common Lisp中最一般的输出函数是format. 它接受两个以上变元:第一个表示输出到哪儿,第二个是字符串模板,剩下的变元通常是对象,它们的打印表示 (printed representation)将被插入到模板中去. 这是个典型的例子:

1
2
3
> (format t "~A plus ~A equals ~A.~%" 2 3 (+ 2 3))
2 plus 3 equals 5.
NIL

注意两样东西打印在这儿. 第一行是format打印的. 第二行是format调用的返回值
在它里面,每个 A*表示一个将被填充的位置, 而 %表示新行符. 这些位置依次被后面的变元的值填充.
标准的输入函数是read. 当没有变元时,它从缺省的地方–通常是顶层–读入. 下面这个函数提示用户输入,然后返回任何输入的东西:

1
2
3
4
5
6
7
8
(defun askem (string)
(format t "~A" string)
(read))

;; 它运行如下:
> (askem "How old are you? ")
How old are you? 29
29

请记住read会永远等在那儿直到你输入什么东西并(通常要)敲入回车. 因此调用 read而不打印明确的提示信息是不明智的,否则你的程序会给人以已经死掉的印象,但实际上它在等待输入.
第二个要了解read的是它非常强大:它是一个完整的Lisp语法分析器. 它并不是读入字符再把它们当作字符串返回. 它分析所读到的东西,并返回所产生的Lisp 对象.

在上例中, 它返回一个数. askem虽然很短,但它展示了一些我们以前在函数定义中没有看到的内容. 它的函数体包含多个表达式. 函数体可以包含任意多个表达式,当函数被调用时,它们依次被求值,函数会返回最后一个表达式的值. 在以前的章节中,我们坚持所谓的``纯粹’’的Lisp–即没有副作用的Lisp. 副作用是指作为表达式求值的后果改变了外部世界的状态. 当我们对一个纯粹的Lisp 表达式,例如(+ 1 2)求值,没有出现副作用;它仅返回一个值. 但当我们调用 format,它不仅返回值,还打印了一些东西. 这是一种副作用.

变量

局部变量

1
2
3
> (let ((x 1) (y 2))
(+ x y))
3

一个let表达式有两部分. 第一部分是一列创造新变量的指令,每个形如(变量 表达式).
最后一个表达式的值作为let的值被返回.

下面是一个使用let 的更具选择性的askem的版本:

1
2
3
4
5
6
(defun ask-number ()
(format t "Please enter a number. ")
(let ((val (read)))
(if (numberp val)
val
(ask-number))))
1
2
3
4
5
> (ask-number)
Please enter a number. a
Please enter a number. (ho hum)
Please enter a number. 52
52

构造全局变量(习惯上全局变量的名字以星号开始和结束)

通过传给defparameter一个符号和一个值

1
2
> (defparameter *glob* 99)
*GLOB*

还可以用defconstant定义全局常数

1
(defconstant limit (+ *glob* 1))

判断当前是否存在全局变量或常数的名字,请用boundp

1
2
> (boundp '*glob*)
T

赋值

Common Lisp中最普通的赋值操作符是setf. 我们可以用它对全局或局部变量进 行赋值:

1
2
3
4
5
6
> (setf *glob* 98)
98
> (let ((n 10))
(setf n 2)
n)
2

如果第一个自变量不是局部变量的名字,它被认为是全局变量:

1
2
> (setf x (list 'a 'b 'c))
(A B C)

即你可以通过赋值隐含地新建全局变量.不过在源文件中明确地使用 defparameter是较好的风格.
你能做的远不止给变量赋值. setf的第一个自变量不但可以是变量名,还可以是 表达式. 在这种情况下,第二个自变量的值被插入到第一个所涉及到的位置:

1
2
3
4
> (setf (car x) 'n) 
N
> x
(N B C)

setf的第一个自变量几乎可以是任何涉及特定位置的表达式. 所有这样的操作符在 附录D中都被标记为``settable’’. 你可以给setf偶数个自变量. 形如

1
2
3
(setf a b 
c d
e f)

的表达式相当于连续三个单独的setf调用:

1
2
3
(setf a b)
(setf c d)
(setf e f)

函数化编程法

函数化编程法的意思是编写通过返回值来工作的程序,而不是修改什么东西.

它是 Lisp中占支配地位的范例. 大多数Lisp内置函数被调用是为了得到它们的返回值, 而不是它们的副作用.

1
2
3
4
> (setf lst '(c a r a t))
(C A R A T)
> (remove 'a lst)
(C R T)

为什么不说remove从表中删除一个对象? 因为这不是它所做的事情. 原来的表没 有被改变:

1
2
> lst
(C A R A T)

那么如果你真想从表中删掉一些元素怎么办? 在Lisp中,你通常这样做类似的事 情:把表传给某个函数,然后用setf来处理返回值. 为了把所有的a从表x中删掉, 我们这样做:

1
(setf x (remove 'a x))

函数化编程法本质上意味着避免使用诸如setf的函数.
完全不利用副作用是有困难的. 但随着学习的深入,你会惊讶地发现真正需要副 作用的地方极少. 你使用副作用越少,你也就越进步.

函数化编程最重要的优点之一是它允许交互式测试. 在纯粹的函数化代码中,当你写函数的时候就可以测试它们. 如果它返回期望的值,你可以肯定它是正确的.

迭代

当我们想做一些重复的事情时,用迭代比用递归更自然些.

1
2
3
4
(defun show-squares (start end)
(do ((i start (+ i 1)))
((> i end) 'done)
(format t "~A ~A~%" i (* i i))))

打印从start到end之间的整数的平方:

1
2
3
4
5
6
> (show-squares 2 5)
2 4
3 9
4 16
5 25
DONE

do宏是Common Lisp中最基本的迭代操作符. 就象let,do也会产生变量,它的第一 个自变量是关于变量规格的表. 表中的每个元素具有如下形式: (variable initial update)

其中variable是符号,而initial和update是表达式. 一开始每个变量会被赋予相 应的initial的值;在迭代的时候它会被赋予相应的update的值.

作为对比,这是递归版本的show-squares:

1
2
3
4
5
6
(defun show-squares (i end)
(if (> i end)
'done
(progn
(format t "~A ~A~%" i (* i i))
(show-squares (+ i 1) end))))

此函数中的唯一新面孔是progn. 它接受任意数量的表达式,对它们依次求值,然后返回最后一个的值.

对一些特殊情况Common Lisp有更简单的迭代操作符. 比如,为了遍历表的所有元素,你更可能用dolist. 这个函数返回表的长度

1
2
3
4
5
6
7
8
9
10
11
12
13
(dolist (n '(1 2 3 4 5 6 7 8 9))
(format t "~% Number: ~d Square: ~d" n (* n n))
)

Number: 1 Square: 1
Number: 2 Square: 4
Number: 3 Square: 9
Number: 4 Square: 16
Number: 5 Square: 25
Number: 6 Square: 36
Number: 7 Square: 49
Number: 8 Square: 64
Number: 9 Square: 81

函数作为对象

函数在Lisp中就象符号,字符串和表一样,是常规的对象. 如果我们给function一个函数的名字,它会返回相关的对象. 就象quote,function是特殊操作符,因此我们不必引用自变量:

1
2
> (function +)
#<Compiled-Function + 17BA4E>

这个模样很奇怪的返回值是函数在典型Common Lisp实现中可能的显示方式. 到目前为止我们涉及到的对象具有这样的特点:Lisp显示它们与我们输入的模样是一致的. 此惯例不适合函数.

我们用’作为quote的简记法,我们可以用#’作为function的简写:

1
2
> #'+
#<Compiled-Function + 17BA4E>

此简记法叫做sharp-quote. 就象其它的对象,函数可以作为自变量传递.
一个接受函数作为自变量的是 apply. 它接受一个函数和一列自变量,并返回那个函数应用这些自变量后的结果:

1
2
3
4
> (apply #'+ '(1 2 3))
6
> (+ 1 2 3)
6

它能接受任意数目的自变量,只要最后一个是表:

1
2
> (apply #'+ 1 2 '(3 4 5))
15

函数funcall能做同样的事情,不过它不需要把自变量放在表中:

1
2
> (funcall #'+ 1 2 3)
6

宏defun创造一个函数并给它一个名字. 但函数并不是必须需要名字,因此我们也不需要用defun来定义它们. 就象其它Lisp对象一样,我们可以直接引用函数.
为了直接引用一个整数,我们用一列数字;为了直接引用函数,我们用所谓的 lambda表达式.
一个lambda表达式是包含以下元素的表:符号lambda,一列参数, 然后是零个或多个表达式组成的函数体. 这个lambda表达式代表接受两个数并返回它们之和的函数:

1
2
(lambda (x y) 
(+ x y))
1
2
> ((lambda (x) (+ x 100)) 1)
101

而通过在lamda表达式之前附加#’,我们得到了相应的函数:

1
2
3
> (funcall #'(lambda (x) (+ x 100))
1)
101

lambda是什么? lambda表达式中的lambda不是操作符. 它仅是个符号. 它在早期的Lisp方言里有一种作用:函数的内部形态是表,因此区别函数和普通表的唯一办法是查看第一个元素是否是符号lambda. 在Common Lisp中你能把函数表示为表,但它们在内部被表示成独特的函数对象. 因此lambda不再是必需的. 如果要求把函数
(lambda (x) (+ x 100))
表示成
((x) (+ x 100))
也没有什么矛盾,但Lisp程序员已经习惯了函数以符号lambda开始,因此Common Lisp保留了此传统.

类型

Lisp用非同寻常的灵活手段来处理类型. 在许多语言中,变量是有类型的,你得指 定变量的类型才能使用它. 在Common Lisp中,值具有类型,而不是变量.

你可以假想每个对象都贴了指明它的类型的标签. 这种方法叫做显示类型. 你不需要去声明变量的类型,因为变量可以装任何类型的对象.

27是类型fixnum,integer,rational,real,number,atom,和t,以一般 性的增长为序. (Numeric类型在第9章中讨论)类型t是所有类型的超集,因此任何对象都是类型t.

函数typep接受一个对象和一个类型说明符,如果对象是那种类型就返回真:

1
2
> (typep 27 'integer)
T

总结

  1. Lisp是交互式语言. 如果你在顶层输入表达式,Lisp会打印它的值.
  2. Lisp程序由表达式组成. 表达式可以是一个原子,或是一个表, 表的第一 个元素是操作符,后面跟着零个或多个自变量. 前缀表达式意味着操作符可接受任意多个自变量.
  3. Common Lisp函数调用的求值规则:从左至右对自变量求值,然后把这些值传给由操作符表示的函数. quote有它自己的求值规则:它原封不动地返回自变量.
  4. 除了通常的数据类型,Lisp还有符号和表. 由于Lisp程序由表组成,很容易编写能写程序的程序.
  5. 三个基本的表处理函数是cons:它创造一个表;car:它返回表的头一个元素; cdr:它返回第一个元素之后的所有东西.
  6. 在Common Lisp里, t表示真,nil表示伪. 在逻辑上下文中,除了nil之外的任何东西都算作真. 基本的条件语句是if. and和or操作符就象条件语句.
  7. Lisp主要是由函数构成的. 你可用defun来定义新的函数.
  8. 调用自己的函数是递归的. 递归函数应该被认为是一个过程而不是机器.
  9. 括号不是个问题,因为程序员利用缩进来读写Lisp.
  10. 基本的i/o函数是read:它包含了完整的Lisp语法分析器,和format:它基于模板产生输出.
  11. 你可以用let创造新的局部变量,用defparameter创造新的全局变量.
  12. 赋值操作符是setf. 它的第一个自变量可以是表达式.
  13. 函数化编程法–它意味着避免副作用–是Lisp中占支配地位的范例.
  14. 基本的循环操作符是do.
  15. 函数是常规的Lisp对象. 它们可以作为自变量被传递,可以表示成lambda 表达式.
  16. 值有类型,而变量没有类型

练习

  1. 解释以下表达式求值后的结果:
    • a. (+ (- 5 1) (+ 3 7))
    • b. (list 1 (+ 2 3))
  2. 给出3种不同的能返回(a b c)的cons表达式
  3. 用car和cdr定义一个函数,它返回表的第四个元素.
  4. 定义一个函数,它接受两个自变量,返回两个中较大的一个.
  5. 这些函数做了什么?
    1
    2
    3
    4
    (defun enigma (x)
    (and (not (null x))
    (or (null (car x))
    (enigma (cdr x)))))
    1
    2
    3
    4
    5
    6
    7
    (defun mystery (x y)
    (if (null y)
    nil
    (if (eql (car y) x)
    0
    (let ((z (mystery x (cdr y))))
    (and z (+ z 1))))))
  6. 在下面的表达式中,x处应该是什么可得出结果?
    1
    2
    > (car (x (cdr '(a (b c) d))))
    B
    1
    2
    > (x 13 (/ 1 0))
    13
    1
    2
    > (x #'list 1 nil)
    (1)
  7. 只用本章介绍的操作符,定义一个函数,它接受一个表作为自变量,并返回t 如果表的元素中至少有一个类型是表.
  8. 给出函数的迭代和递归版本:它
    • a. 接受一个正整数,并打印这么多数目的点.
    • b. 接受一个表,返回符号a在表中出现的次数.
  9. 一位朋友想写一个函数,它返回表中所有非nil元素之和. 他写了此函数的 两个版本, 但没有一个能正确工作. 请指出错误在哪里,并给出正确的版本:
    1
    2
    3
    (defun summit (lst)
    (remove nil lst)
    (apply #'+ lst))
    1
    2
    3
    4
    5
    (defun summit (lst)
    (let ((x (car lst)))
    (if (null x)
    (summit (cdr lst))
    (+ x (summit (cdr lst))))))

答案
1.
- 14
- (1 5)
1.

1
2
3
(cons 'a (cons 'b (cons 'c nil)))
(cons 'a '(b c))
(cons 'a (cons 'b '(c)))

1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defun getForth (lst)
(car (cdr (cdr (cdr lst)))))

(defun getForth (lst n)
(let ((result lst))
(do ((i 2 (+ i 1)))
((> i n) 'done)
(setf result (cdr result))
)
(car result)
)
)

(getFour '(1 2 3 4 5 6 7 8 9 11) 5)

1.
1
2
3
4
5
6
7
(defun getMax (a b)
(if (> a b)
(format t "~A" a)
(format t "~A" b)
)
)
(getMax 1 3)

1.
- 判断 x 列表中是否有 nil 元素
- 查找 x 在列表 y 中的下标,如果没有则为 nil
1.
- car
- or
- or ‘(1) 或 apply
1.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
(defun checkList (x)
(and x
(or (listp (car x))
(checkList (cdr x)))))

(defun checkList (x)
(if (null x)
nil
(if (listp (car x))
t
(checkList (cdr x)))))

; (checkList '(1 2)) // nil
; (checkList '(1 2 '(1))) // T

1.
1
2
3
4
5
6
7
8
; 接受一个正整数,并打印这么多数目的点---递归
(defun print-point (x)
(if (= 0 x)
nil
(progn (format t "~%.")
(print-point (- x 1)))))

> (print-point 5)

1
2
3
4
5
6
7
; 接受一个正整数,并打印这么多数目的点---迭代
(defun print-point (x)
(do ((i 0 (+ i 1)))
((= i x) 'done)
(format t "~%.")))

> (print-point 5)

1
2
3
4
5
6
7
8
; 接受一个列表,并返回 a 在列表里所出现的次数--递归
(defun count-a (lst)
(if (null lst)
0
(+ (if (eql 'a (car lst)) 1 0)
(count-a (cdr lst)))))

> (count-a '(a a a 1 3 4 a))

1
2
3
4
5
6
7
8
9
10
; 接受一个列表,并返回 a 在列表里所出现的次数--迭代
(defun count-a (lst)
(let ((count 0))
(dolist (n lst)
(setf count
(+ (if (eql 'a n) 1 0)
count)))
count))

(count-a '(a a a a 1 23))

1.
1
2
3
4
; 因为 remove 并不会改变 lst 本身
(defun summit (lst)
(setf nlst (remove nil lst))
(apply #'+ nlst))

1
2
3
4
5
6
7
8
; 因为递归没有边界退出分支
(defun summit (lst)
(if (null lst)
0
(let ((x (car lst)))
(if (null x)
(summit (cdr lst))
(+ x (summit (cdr lst)))))))