Intro

  i i i i i i i       ooooo    o        ooooooo   ooooo   ooooo
  I I I I I I I      8     8   8           8     8     o  8    8
  I  \ `+' /  I      8         8           8     8        8    8
   \  `-+-'  /       8         8           8      ooooo   8oooo
    `-__|__-'        8         8           8           8  8
        |            8     o   8           8     o     8  8
  ------+------       ooooo    8oooooo  ooo8ooo   ooooo   8

Welcome to GNU CLISP 2.49.93+ (2018-02-18) 

交互式的(类似 ipython), 在 terminal 中输入 ecl (Embeddable Common Lisp) . 没有 的话则需要安装. 也可以用 clisp

此为快速入门笔记.

Common Lisp Syntax

Form

lisp 的语法比较特别, 表达式用括号括起来, 括号中第一部分是函数, 剩余部分是函数的 参数. 当然也可以直接是一个 data. 如

> 1

1
> (+ 2 3)

5
> (+ 2 3 4)

9
> (+)

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

3

Evaluation

lisp 默认对 (+ 1 1) 是要求值的. 如果要避免求值, 可以用 quote .

> (quote (+ 3 5))

(+ 3 5)

'quote 的简写

> '(+ 3 5)

(+ 3 5)

Data

integer, string

在其它语言中常见.

symbol

无论怎输入, 都会被转换成大写. symbol 不对自身求值, 一般加 ' 引用

> 'Good

GOOD

list

> (list 'hello (+ 3 6) "good")

(HELLO 9 "good")

list 函数来创建列表. build lists

> (list '(+ 1 1) (+ 1 1))

((+ 1 1) 2)

空列表有两种表示方式

> ()

NIL
> nil

NIL 

List Operations

cons build lists

> (cons 'a '(b c d))

(A B C D)

上述例子第二个实参是一个 list .

list 是将几个元素加到 nil 空表的快捷方式, 如

> (cons 'a (cons 'b nil))

(A B)
> (list 'a 'b)

(A B)

car 返回 list 的第一个元素, cdr 返回第一个元素之后的所有元素, 如

> (car '(a b c))

A
> (cdr '(a b c))

(B C)

取第三个元素有两种方法, 如

> (car (cdr (cdr '(a b c d))))

C
> (third '(a b c d))

C

Truth

True and False

symbol t 表示逻辑真的默认值. 它是一个 symbol, 因此它会自身求值. 逻辑假由 nil 来表示.

The function listp returns true if its argument is a list:

> (listp '(a b c))

T
> (listp 2)

NIL

listp 这样返回结果为真或假的函数, 称为 predicate, 这类函数通常以 p 结尾.

The function null returns true of the empty list. The function not returns true if its argument is false. 如

> (null nil)

T
> (not nil)

T

上述例子中, 第一个 nil 表示 empty list, 因此返回 True. 第二个 nil 表示逻辑假, 因此返回 True. 但由于 nil 既可表示 empty list, 也可表示逻辑假, 因此上述两个表 达式在功能上等价的.

if

> (if (listp '(a b c))
      (+ 1 2)
      (+ 5 6))

3
> (if (listp 2)
      (+ 1 2)
      (+ 5 6))

11

if 函数的第一个参量是 test 表达式, 即要判断真假的对象. 第二个 then 表达式. 第三 个是 else 表达式, 是可选参数, 默认是 nil . 如

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

NIL

除了 nil 以外的所有东西, 都视为 True, 如

> (if 27 1 2)

1

AND and OR

逻辑与, 或. and , 求到第一个为 False 后, 就不对后面的表达式求值了, 直接返回 nil 如果所有值为真, 那么它就会返回最后一个参数(而不是返回 True). 也就是说它比 较懒, 知道结果了, 就不继续求值了. . or 运算时, 有一个 False 后, 就不对后面的表达式求值了.

> (and t (+ 1 2))

3

Functions

定义 function, 第一个实参是函数名字, 第二个是用列表表示的参数, 第三个是一个或多 个组成函数体的表达式. 如

> (and t (+ 1 2))

3
> (defun our-third (x)
   (car (cdr (cdr x))))

OUR-THIRD
> (our-third '(a b c d e f))

C

又如

> (defun sum-greater (x y z)
   (> (+ x y) z))

SUM-GREATER
> (sum-greater 1 4 3)

T
> (sum-greater 2 5 9)

NIL

Recursion

下面的函数是递归的一个例子

> (defun our-member (obj lst)
   (if (null lst)
       nil
   (if (eql (car lst) obj)
       lst
       (our-member obj (cdr lst)))))

OUR-MEMBER
> (our-member 'b '(a b c))

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

NIL

函数 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))
2 plus 3 equals 5. 
NIL

~A 表示被填入的位置, ~% 表示换行.

input

> (defun askem (string)
   (format t "~A" string)
   (read))

ASKEM
> (askem "How old are you?")
How old are you?15

15

这个函数首先输出参量 string , 返回通过 read 输入得到的值. 当函数 read 没有 实参时, 它会读取默认的位置. 函数 askem 有两个表达式, 它会返回最后一个表达式的 值.

Variables

local variable

let 可以引入局部变量, 如

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

3

上述例子分别把 12 赋值给 xy , 赋值只在 let 函数值内有效. 之后 是表达式, 求值的结果作为 let 的返回值.

>  (defun ask-number ()
   (format t "Please enter a number. ")
   (let ((val (read)))
     (if (numberp val)
         val
          (ask-number))))

ASK-NUMBER
> (ask-number)
Please enter a number. a
Please enter a number. (ho hum)
Please enter a number. 19

numberp 是一个 predicate, 判断是否是一个数. 这也是递归调用的一个例子.

global variable

defparameter 来定义全局变量, 由 defconstant 定义全局常量, 由 boundp 判 断某个符号是否为一个全局变量或常量. 如

> (defparameter *glob* 99)

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

LIMIT
> (boundp '*glob*)

T

Assignment

setf 来给变量赋值. 如

> (setf *glob* 98)

98
> (let ((n 10))
    (setf n 2)
    n)

2

如果一个 symbol 不是局部变量的名字, 那么 setf 把这个 symbol 设置为全局变量. 如

> (setf x (list 'a 'b 'c))

(A B C)

也就是说 setf 也可以用来创建全局变量, 不过还是推荐用 defparameter 来创建, 这 样比较明确.

setf 还有一个用法. 第一个实参可以是表达式, 这样第二个实参直接传给表达式中. 如

> (setf (car x) 'n)

N
> x

(N B C)

以下两种表达方式是等价的

> (setf a 'b
        c 'd
        e 'f)

F
> a

B
> c

D
> e

F
> (setf a 'b)

B
> (setf c 'd)

D
> (setf e 'f)

F

Functional Programming

Lisp 的主流范式是函数式编程. 中心思想是: 执行一个函数是得到它的返回值. 如

> (setf lst '(c a r a t))

(C A R A T)
> (remove 'a lst)

(C R T)
> lst

(C A R A T)

remove 函数是移除列表中的指定元素. 但只是返回移除之后的结果, 原来的列表还是原 来的列表. 如果真的想要移除, 可以如下操作

> (setf lst (remove 'a lst))

(C R T)

函数式编程意味着避免使用如 setf 一样的函数. 它的优点之一是允许 interactive testing.

Iteration

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

SHOW-SQUARES
> (show-squares 2 5)
2 4
3 9
4 16
5 25
DONE

do 的第一个表达式是 (variable initial update) , 标明变量 i , 初值, 更新规 则. 第二个表达式是结束的条件. 剩下的是循环体.

上述函数也可以用递归来写, 但是不太自然

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

SHOW-SQUARES
> (show-squares 2 5)
2 4
3 9
4 16
5 25
DONE

新的函数 progn 接受任意数量的表达式, 依次求值, 并返回最后一个表达式的值.

dolist 来遍历列表元素会更加简单

> (defun our-length (lst)
    (let ((len 0))
      (dolist (obj lst)
        (setf len (+ len 1)))
     len))

OUR-LENGTH
> (our-length (list 'a 'b 'c))

3

上述例子的递归版本为

> (defun our-length (lst)
    (if (null lst)
        0
        (+ (our-length (cdr lst)) 1)))

OUR-LENGTH
> (our-length (list 'a 'b 'c 'd))

4
> (our-length 'nil)

0
> (our-length '(a b c))          

3

它更容易理解, 但由于不是 tail-recursive 的形式, 效率不是那么高.

Functions as Objects

function 是一个特殊的操作符号, 如果把函数的名字传给function, 它会返回相关关联 的对象, 如

> (function +)

#

#' (sharp-quote)作为 function 的缩写, 如

> #' +

#

apply 可以接受函数作为第一个实参, 第二个列表作为函数的实参. 如

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

6

funcall 做相同的事情, 但不需要把实参包装成列表

> (funcall #'+ 1 2 3)

6

lambda

lambda 不是一个操作符, 而只是一个符号. 早期由于函数在内部是用列表表示的, 因此 将函数的第一个元素标记为 lambda 加以区分. 如

> (lambda (x) (+ x 100))

#

(书上说现在可以省略 lambda , 但是我这里省略了会报错, 或许我的版本不够新?)

lambda 表达式是一个列表, 包含符号 lambda , 接着是形参列表, 以及由零个或多个 表达式所组成的函数体. 如

> (lambda (x y))

#
> (lambda (x y)
    (+ x y))

#
> ((lambda (x) (+ x 100)) 1)

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

101

Types

变量没有类型, 数值才有类型, 且可有多个类型. 如 27 的类型, 依普遍性增加顺序为 fixnum , integer , rational , real , number , atom , t . t 是所 有类型的 supertype, 所以每个对象都属于 t 类型. 如用 typep 来判断某个数值是否 为某个类型

> (typep 27 'integer)

T
> (typep 27 't)

T
> (typep 27 'rational)

T
> (typep 27 'fixnum)

T

...

赶在因疫情突然决定明天回家之前整理完此篇. 不禁感叹世事无常, 还是要好好珍惜眼前的 人和事啊!

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")

#+RESULTS: : 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)

#+RESULTS: : Hello, Emacser!

变量也可以加 doc string, 可用 C-h v 查看. 如声明变量

(defvar foo "I'm foo!"
  "A demo variable")
foo

#+RESULTS: : I'm foo

Some functions

函数 eq 用来判断变量是否为某个值. 如( elisplisp 语法还是有些不同, 如 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)

#+RESULTS: : t, nil, t, nil

system-type

#+RESULTS: : gnu/linux

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/