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/