Intro
i i i i i i i ooooo o ooooooo ooooo ooooo |
交互式的(类似 ipython), 在 terminal 中输入 ecl
(Embeddable Common Lisp) . 没有
的话则需要安装. 也可以用 clisp
此为快速入门笔记.
Common Lisp Syntax
Form
lisp 的语法比较特别, 表达式用括号括起来, 括号中第一部分是函数, 剩余部分是函数的 参数. 当然也可以直接是一个 data. 如
> 1 |
Evaluation
lisp 默认对 (+ 1 1)
是要求值的. 如果要避免求值, 可以用 quote
.
> (quote (+ 3 5)) |
'
为 quote
的简写
> '(+ 3 5) |
Data
integer, string
在其它语言中常见.
symbol
无论怎输入, 都会被转换成大写. symbol 不对自身求值, 一般加 '
引用
> 'Good |
list
> (list 'hello (+ 3 6) "good") |
list
函数来创建列表. build lists
> (list '(+ 1 1) (+ 1 1)) |
空列表有两种表示方式
> () |
List Operations
cons
build lists
> (cons 'a '(b c d)) |
上述例子第二个实参是一个 list .
list 是将几个元素加到 nil
空表的快捷方式, 如
> (cons 'a (cons 'b nil)) |
car
返回 list 的第一个元素, cdr
返回第一个元素之后的所有元素, 如
> (car '(a b c)) |
取第三个元素有两种方法, 如
> (car (cdr (cdr '(a b c d)))) |
Truth
True and False
symbol t
表示逻辑真的默认值. 它是一个 symbol, 因此它会自身求值. 逻辑假由 nil
来表示.
The function listp
returns true if its argument is a list:
> (listp '(a b c)) |
像 listp
这样返回结果为真或假的函数, 称为 predicate, 这类函数通常以 p 结尾.
The function null
returns true of the empty list. The function not
returns
true if its argument is false. 如
> (null nil) |
上述例子中, 第一个 nil
表示 empty list, 因此返回 True. 第二个 nil
表示逻辑假,
因此返回 True. 但由于 nil
既可表示 empty list, 也可表示逻辑假, 因此上述两个表
达式在功能上等价的.
if
> (if (listp '(a b c)) |
if 函数的第一个参量是 test 表达式, 即要判断真假的对象. 第二个 then 表达式. 第三
个是 else 表达式, 是可选参数, 默认是 nil
. 如
> (if (listp 27) |
除了 nil
以外的所有东西, 都视为 True, 如
> (if 27 1 2) |
AND and OR
逻辑与, 或. and
, 求到第一个为 False 后, 就不对后面的表达式求值了, 直接返回
nil
如果所有值为真, 那么它就会返回最后一个参数(而不是返回 True). 也就是说它比
较懒, 知道结果了, 就不继续求值了.
. or 运算时, 有一个 False 后, 就不对后面的表达式求值了.
> (and t (+ 1 2)) |
Functions
定义 function, 第一个实参是函数名字, 第二个是用列表表示的参数, 第三个是一个或多 个组成函数体的表达式. 如
> (and t (+ 1 2)) |
又如
> (defun sum-greater (x y z) |
Recursion
下面的函数是递归的一个例子
> (defun our-member (obj lst) |
函数 our-member
用来判断 obj
是否是列表 lst
中的成员.
首先判断 lst
是否为空, 如果为空, 那当然就返回 nil
. 如果不空, 那么就用函数
eql
判断 lst
的第一个成员是否与 obj
相同, 相同的话输出当前的 lst
, 如果
不同, 只有当 obj
是其它列表成员时, 它才可能是 lst
的成员, 于是就就递归调用,
并把除掉第一个成员后的 lst
传递给递归调用的自己.
Input and Output
output
common lisp 最普遍的输出函数是 format
. 第一个实参是输出到哪里, t
表示默认的
的地方. 第二个实参是字符模板, 剩下的实参是要插入到模板的对象. 如
> (format t "~A plus ~A equals ~A. ~%" 2 3 (+ 2 3)) |
~A
表示被填入的位置, ~%
表示换行.
input
> (defun askem (string) |
这个函数首先输出参量 string
, 返回通过 read
输入得到的值. 当函数 read
没有
实参时, 它会读取默认的位置. 函数 askem
有两个表达式, 它会返回最后一个表达式的
值.
Variables
local variable
let
可以引入局部变量, 如
> (let ((x 1) (y 2)) |
上述例子分别把 1
和 2
赋值给 x
和 y
, 赋值只在 let
函数值内有效. 之后
是表达式, 求值的结果作为 let
的返回值.
> (defun ask-number () |
numberp
是一个 predicate, 判断是否是一个数. 这也是递归调用的一个例子.
global variable
由 defparameter
来定义全局变量, 由 defconstant
定义全局常量, 由 boundp
判
断某个符号是否为一个全局变量或常量. 如
> (defparameter *glob* 99) |
Assignment
setf
来给变量赋值. 如
> (setf *glob* 98) |
如果一个 symbol 不是局部变量的名字, 那么 setf
把这个 symbol 设置为全局变量. 如
> (setf x (list 'a 'b 'c)) |
也就是说 setf
也可以用来创建全局变量, 不过还是推荐用 defparameter
来创建, 这
样比较明确.
setf
还有一个用法. 第一个实参可以是表达式, 这样第二个实参直接传给表达式中. 如
> (setf (car x) 'n) |
以下两种表达方式是等价的
> (setf a 'b |
> (setf a 'b) |
Functional Programming
Lisp 的主流范式是函数式编程. 中心思想是: 执行一个函数是得到它的返回值. 如
> (setf lst '(c a r a t)) |
remove
函数是移除列表中的指定元素. 但只是返回移除之后的结果, 原来的列表还是原
来的列表. 如果真的想要移除, 可以如下操作
> (setf lst (remove 'a lst)) |
函数式编程意味着避免使用如 setf
一样的函数. 它的优点之一是允许 interactive
testing.
Iteration
如
> (defun show-squares (start end) |
do
的第一个表达式是 (variable initial update)
, 标明变量 i
, 初值, 更新规
则. 第二个表达式是结束的条件. 剩下的是循环体.
上述函数也可以用递归来写, 但是不太自然
> (defun show-squares (i end) |
新的函数 progn
接受任意数量的表达式, 依次求值, 并返回最后一个表达式的值.
用 dolist
来遍历列表元素会更加简单
> (defun our-length (lst) |
上述例子的递归版本为
> (defun our-length (lst) |
它更容易理解, 但由于不是 tail-recursive 的形式, 效率不是那么高.
Functions as Objects
function
是一个特殊的操作符号, 如果把函数的名字传给function, 它会返回相关关联
的对象, 如
> (function +) |
#'
(sharp-quote)作为 function 的缩写, 如
> #' + |
apply
可以接受函数作为第一个实参, 第二个列表作为函数的实参. 如
> (apply #'+ '(1 2 3)) |
funcall
做相同的事情, 但不需要把实参包装成列表
> (funcall #'+ 1 2 3) |
lambda
lambda
不是一个操作符, 而只是一个符号. 早期由于函数在内部是用列表表示的, 因此
将函数的第一个元素标记为 lambda
加以区分. 如
> (lambda (x) (+ x 100)) |
(书上说现在可以省略 lambda
, 但是我这里省略了会报错, 或许我的版本不够新?)
lambda
表达式是一个列表, 包含符号 lambda
, 接着是形参列表, 以及由零个或多个
表达式所组成的函数体. 如
> (lambda (x y)) |
Types
变量没有类型, 数值才有类型, 且可有多个类型. 如 27
的类型, 依普遍性增加顺序为
fixnum
, integer
, rational
, real
, number
, atom
, t
. t
是所
有类型的 supertype, 所以每个对象都属于 t
类型. 如用 typep
来判断某个数值是否
为某个类型
> (typep 27 'integer) |
…
赶在因疫情突然决定明天回家之前整理完此篇. 不禁感叹世事无常, 还是要好好珍惜眼前的 人和事啊!
Elisp
Hello wolrd
elisp 要在 emacs 中执行. 一个 hello world 的例子. 在 emacs 中 M-x
lisp-interaction-mode
切换到 lisp 交互主模式, 写入
(message "hello world") |
光标切到行尾, C-j
即可运行. 另外也可以在 org-mode
中, 插入 elisp
代码块,
然后 C-c
C-c
运行. (原 org 文件可以显示结果, hexo 不渲染执行结果) .
(message "hello world") |
Doc string
函数可以加 doc string, 将光标移到函数上, 用 C-h
f
查看. 如
(defun hello-world (name) "Say hello to user whose name is NAME" (message "Hello, %s!" name)) (hello-world 'Emacser) |
变量也可以加 doc string, 可用 C-h
v
查看. 如声明变量
(defvar foo "I'm foo!" "A demo variable") foo |
Some functions
函数 eq
用来判断变量是否为某个值. 如( elisp
和 lisp
语法还是有些不同, 如
format
)
(defun eq-example() "A demo for function eq" (let ((a 1) (b 'x)) (format "%s, %s, %s, %s" (eq a 1) (eq a 2) (eq b 'x) (eq b 'y)))) (eq-example) |
system-type |
Reference
- Book: ANSI Common LISP by Paul Graham (z-lib.org)
- https://acl.readthedocs.io/en/latest/zhCN/ch2-cn.html
- http://smacs.github.io/elisp/01-hello-world.html
- https://www.emacswiki.org/emacs/