clisp 学习笔记 1-简述
现在看来,学习 Lisp 更多是为了学习而非工程实践,因此当时我实在不应该选择 Common Lisp,它工程味道太浓了。将来再要去接触 Lisp,首选 Racket,而若是为了工程实践,则 Clojure。
——2022.01.02
…… 我决定去学 Haskell 了,再见,Racket!
…… 我决定去学 Racket 了,再见,Clojure!
…… 我决定去学 Clojure 了,再见,common lisp!
你好,common lisp,我又决定先来找你玩了!
我又决定去学 Haskell 和 Clojure 了,再见,common Lisp!
哈哈哈哈哈哈哈
QA
Q:为什么学 lisp?
A:它酷!
Q:酷有个屁用?
A:****!
Q:根据哪里学的?
A:ANSI Common Lisp
lisp 语言的形式
1 |
|
lisp 语言只有一种形式(数据和代码都采用这种格式)(不算注释的话)——上述的前序表达式。
lisp 语言必须使用括号包裹。否则在某些时候无法判断表达式什么时候结束,比如——
1 |
|
求值
lisp 是这样求值的——
- 首先从左至右对实参求值。
- 实参的值传入以操作符命名的函数。
在这个例子中——
1 |
|
lisp 对 2 求值,结果为 2,对 3 求值,结果为 3,对 4 求值,结果为 4,对 5 求值,结果为 5,然后将这些值传给+函数,返回 14。
数据
符号 symbol 和列表 lists
符号是单词(words),其不能(无法?)对自身求值,应当使用’引用。
由括号包裹的零个或多个元素称为列表。列表应该使用’来引用,因为其结构和函数调用的结构相同,lisp 会认为这是函数调用。
1 |
|
quote 函数(它不遵守 CLisp 既定的求职规则),它和引用起相同作用。
1 |
|
列表表示 lisp 程序
lisp 的程序是用列表来表示的,这是 lisp 最卓越的特性。lisp 的代码如果被引用,就对自身求值,如果没有,则被视为代码,按照求值规则求值后返回值。
1 |
|
因此,lisp 代码是容易写出 lisp 代码的。并且 lisp 程序员应经常地使用此特性来解决问题,“让 lisp 适应问题,而非让问题适应 lisp”。
NIL
lisp 有两种方式表示空列表。
1 |
|
列表操作
cons
cons 的第二个实参是列表(记住,列表是要引用的,否则它会被当成函数调用),它将第一个实参加入到第二个实参这个列表前,得到一个新列表。
1 |
|
list
list 将所有实参加入到一个列表中。
1 |
|
car & cdr
car 返回第一个元素,cdr 返回第一个元素后的所有元素(以列表的形式)。
1 |
|
混合使用 car 和 cdr 可以取到列表中的任何元素。
真假
谓词(predicate)
谓词是 lisp 中返回真假的函数,就像其他语言的返回布尔值的函数。一般而言,其结尾是 p。
1 |
|
not & null
t 表示逻辑真,t 也是对自身求值的,与 nil 相同。
逻辑假用 nil 表示。
1 |
|
not 和 null 在逻辑上是不同的,前者判断是否是逻辑假,后者判断是否是空表,可是实际上其结果是相同的。
1 |
|
条件表达式 if
if 接受三个实参,一个 test 表达式,一个 then 表达式,一个 else 表达式。if 会先对 test 表达式求值,如果为真,则求 then,否则求 else。
1 |
|
if 也是特殊的操作符,如同 quote。if 不遵守求值规则之处在于,它对最后两个实参,只会求其中一个的值。因此,if 是不能用函数来实现的。
要注意的是,非 nil 的,都是 t
and & or
and 和 or 都从左往右求值。但是,and 在遇到 NIL 时停止求值,返回 NIL 或最后一个参数的值,如果其他参数全为真
or 在遇到参数为逻辑真时停止求值,返回这个值
1 |
|
and 和 or 这两个操作符称为宏,它们和特殊的操作符一样,可以绕过一般的求值规则。
函数
定义新函数的函数叫 defun(它显然也是不遵守求值规则的),它接受三个以上的实参——一个名字,一组用列表表示的实参(而这个实参中的内容将被作为函数的形参),以及一个或多个组成函数体的表达式。
1 |
|
可见,在进行函数调用时,它会把函数名进行求值,转换成全大写的 symbol。
符号(symbol)就是变量(和函数)的名字,符号本身以对象存在。因此,符号和列表必须要以引用形式来使用,否则列表会被当成函数,符号会被当成变量。
递归
没啥可说的。
判断一个 tar 是否在一个列表中,如果是,返回这个 tar 及以后的子列表,否则返回 NIL。
1 |
|
IO
输出
最普遍的 clisp 输出函数时 format,接受两个两个及以上的实参,第一个实参决定要打印到的位置,第二个实参是字符串模板,剩余的实参通常是要插入到字符串模板。
t 表示输出送到缺省的地方(通常是顶层,或者是控制台?),A 表示被填入的位置,%表示换行符。
1 |
|
输入
clisp 的标准的输入函数是 read,当没有实参时,会读取缺省的位置,通常是顶层。它会返回输入的东西被处理后的结果。
read 非常强大,虽然还不知道到底如何强大 w
1 |
|
变量和赋值
let
let 引入局部变量,它只在 let 的括号内有效。
let 表达式有两个部分,第一个部分是创建新变量,其格式为 (variable expression)。每一格变量会被赋予相对应表达式的值。
1 |
|
写一个要求用户输入数字的函数,如果输入的是数字,则返回它,否则返回该函数。
之后,用>标识返回值。
1 |
|
defparameter & defconstant
1 |
|
setf
setf 是最普遍的赋值操作符。setf 的第一个实参可以为全局或局部变量,可以为符号,可以为表达式。
1 |
|
任何引用到特定位置的表达式都可以做 setf 的第一个实参。
函数式编程
函数式编程意味着只利用返回值,不造成副作用,函数式编程是 lisp 的主流范式,大部分 lisp 的内置函数都是函数式的。
1 |
|
副作用越少,技术越高 w,这意味着尽量少用 setf 这类的函数。
迭代
迭代总是比递归自然的……
do
do 宏是 clisp 的最基本的迭代操作符,和 let 类似,do 也可以创建局部变量。
第一个实参是一组变量的规格说明列表,每个元素是这样的形式 (varible initial update)。
第二个实参包含一个或多个表达式,第一个表达式测试迭代是否结束,最后一个表达式是 do 的返回值。
其余实参是 do 循环的函数体。
1 |
|
dolist
dolist 更为简单,它遍历一个列表。
1 |
|
函数作为对象
把函数当成变量一样的对象看待,是稀松平常的,js 喜欢这个。
1 |
|
#’是 function 的缩写,就如同’是 quote 的缩写一样,
apply
函数可以作为实参传入。如 apply 函数。它接受任意数量的实参,但是要求第一个实参是函数,最后一个实参是列表。
1 |
|
funcall
funcall 进行一样的工作,但是不需要把实参包装成列表。
1 |
|
lambda
lambda,匿名函数一样的东西。它似乎不能赋值给变量
1 |
|
类型
lisp 的变量默认没有类型(当然,值有类型)。
lisp 的内置类型组成了一个类的层级,任何对象总是不止属于一个类型。
比如,数字 27,按普遍性递增排序,其类型依次是 fixnum 、 integer 、 rational 、 real 、 number 、 atom 、t。
t 是所有类型的基类,每个对象都属于 t。
谓词 typep 接受一个对象和一个类型,判定对象是否为该类型,如果是,返回真。
1 |
|
展望
lisp 是适合写 lisp 的语言,写代码的时候,不只是在语言中编程,也是在让语言适合程序。
lisp 的语法单一,一切都基于列表。lisp 本身就是 lisp 程序。
这一切是 lisp 最最典型,最最优雅的特性。
每一个 lisp 程序员都应当善用 lisp 的这些特性,在适应 lisp 的同时,也让 lisp 适应自己……
花里胡哨的!
习题
- 描述下列表达式求值之后的结果:
(a) (+ (- 5 1) (+ 3 7))
(b) (list 1 (+ 2 3))
(c) (if (listp 1) (+ 1 2) (+ 3 4))
(d) (list (and (listp 3) t) (+ 1 2))
1 |
|
- 给出 3 种不同表示 (a b c) 的 cons 表达式。
1 |
|
- 使用 car 与 cdr 来定义一个函数,返回一个列表的第四个元素。
1 |
|
- 定义一个函数,接受两个实参,返回两者当中较大的那个。
1 |
|
- 这些函数做了什么?
1 |
|
1 |
|
- 下列表达式, x 该是什么,才会得到相同的结果?
(a) > (car (x (cdr ‘(a (b c) d))))
B
(b) > (x 13 (/ 1 0))
13
(c) > (x #’list 1 nil)
(1)
1 |
|
- 只使用本章所介绍的操作符,定义一个函数,它接受一个列表作为实参,如果有一个元素是列表时,就返回真,
1 |
|
- 给出函数的迭代与递归版本。
a. 接受一个正整数,并打印出数字数量的点。
b. 接受一个列表,并返回 a 在列表里所出现的次数。
1 |
|
- 一位朋友想写一个函数,返回列表里所有非 nil 元素的和。他写了此函数的两个版本,但两个都不能工作。请解释每一个的错误在哪里,并给出正确的版本。
1 |
|
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!