Common Lisp SBCL 快速教程

Steel Bank Common Lisp 入门指南

目录

1. SBCL简介和安装

SBCL (Steel Bank Common Lisp) 是一个高性能的Common Lisp实现,它源自于CMUCL项目,是一个开源、免费且功能强大的Common Lisp开发环境。SBCL以其优秀的编译器和运行时系统而闻名,提供了高效的代码执行和丰富的调试功能。

安装SBCL

SBCL支持多种操作系统,包括Windows、Linux和macOS。以下是不同平台的安装方法:

Linux系统

在基于Debian/Ubuntu的系统上,可以使用以下命令安装:

sudo apt-get update
sudo apt-get install sbcl

在基于Red Hat/Fedora的系统上,可以使用以下命令安装:

sudo dnf install sbcl

macOS系统

使用Homebrew安装:

brew install sbcl

Windows系统

从SBCL官方网站(http://www.sbcl.org/)下载Windows安装程序,然后按照安装向导进行安装。

注意:安装完成后,可以在命令行中输入sbcl来启动SBCL REPL(Read-Eval-Print Loop,交互式解释环境)。

2. 开发环境搭建

虽然SBCL自带了REPL环境,但对于更复杂的开发工作,推荐使用Emacs + SLIME(Superior Lisp Interaction Mode for Emacs)的组合,这是Common Lisp开发中最流行的环境。

安装Emacs

Emacs是一个强大的文本编辑器,对Lisp有着天然的支持。安装方法如下:

Linux系统

# Debian/Ubuntu
sudo apt-get install emacs

# Red Hat/Fedora
sudo dnf install emacs

macOS系统

brew install emacs

Windows系统

从Emacs官方网站(https://www.gnu.org/software/emacs/)下载Windows版本并安装。

安装SLIME

SLIME(Superior Lisp Interaction Mode for Emacs)是Emacs的一个模式,提供了强大的Common Lisp开发环境。安装步骤如下:

  1. 安装Quicklisp(Common Lisp的库管理器):
    curl -O https://beta.quicklisp.org/quicklisp.lisp
    sbcl --load quicklisp.lisp --eval '(quicklisp-quickstart:install)' --eval '(ql:add-to-init-file)' --quit
  2. 在Emacs中安装SLIME:
    M-x package-install RET slime RET
  3. 配置Emacs的初始化文件(~/.emacs或~/.emacs.d/init.el):
    (setq inferior-lisp-program "sbcl")
    (load (expand-file-name "~/quicklisp/slime-helper.el"))
    (slime-setup '(slime-fancy slime-tramp slime-asdf))

提示:配置完成后,在Emacs中输入M-x slime即可启动SLIME环境,与SBCL进行交互。

3. Common Lisp基本语法

Common Lisp使用前缀表达式(Prefix Notation)来表示代码和数据,所有的表达式都是S-表达式(Symbolic Expression),即括号包围的列表。这种统一的语法结构是Lisp语言的特色之一。

Hello World示例

在Common Lisp中,有多种方式输出"Hello, World!":

直接输出字符串
"Hello, World!"

这种方式只是简单地返回字符串,并不是真正的输出。

使用format函数输出
(format t "Hello, World!")

这是最常用的输出方式,format函数的第一个参数t表示输出到标准输出(终端)。

定义并调用函数
(defun hello-world ()
  (format t "Hello, World!"))

(hello-world)

这种方式定义了一个名为hello-world的函数,然后调用它。

基本语法元素

表达式和求值

Common Lisp中,所有的代码都是表达式,每个表达式都会被求值。表达式可以是原子(如数字、字符串、符号)或列表。

;; 数字自求值
10  ; 返回 10

;; 字符串自求值
"Hello, World!"  ; 返回 "Hello, World!"

;; 函数调用:列表的第一个元素是函数名,其余是参数
(+ 2 3)  ; 返回 5

;; 嵌套表达式
(* (+ 2 3) (- 5 1))  ; 返回 20

变量定义

使用defparameterdefvar定义全局变量:

(defparameter *pi* 3.14159)  ; 定义全局变量*pi*
(defvar *name* "Common Lisp")  ; 定义全局变量*name*

注意:按照Common Lisp的约定,全局变量通常以星号(*)包围,称为"耳蜗变量"(earmuffs)。

局部变量

使用let定义局部变量:

(let ((x 10)
      (y 20))
  (+ x y))  ; 返回 30

条件语句

使用if进行条件判断:

(if (> x 0)
    (format t "x是正数")
    (format t "x不是正数"))

使用cond进行多条件判断:

(cond ((> x 0) (format t "x是正数"))
      ((< x 0) (format t "x是负数"))
      (t (format t "x是零")))

循环

使用loop进行循环:

(loop for i from 1 to 10
      do (format t "~a " i))  ; 输出 1 2 3 4 5 6 7 8 9 10

使用dolist遍历列表:

(dolist (item '(a b c d))
  (format t "~a " item))  ; 输出 A B C D

4. 函数定义和使用

在Common Lisp中,函数是一等公民,可以像其他数据类型一样被传递和操作。使用defun宏来定义函数。

基本函数定义

使用defun定义函数的基本语法:

(defun 函数名 (参数*)
  "可选的函数描述"
  函数体*)
计算圆的面积
(defun area (r)
  "计算圆的面积"
  (* pi r r))

;; 调用函数
(area 5)  ; 返回 78.539816

可选参数

使用&optional关键字定义可选参数:

(defun make-rectangle (width &optional (height 10))
  "创建一个矩形,高度默认为10"
  (list width height))

(make-rectangle 5)      ; 返回 (5 10)
(make-rectangle 5 8)    ; 返回 (5 8)

关键字参数

使用&key关键字定义关键字参数:

(defun make-person (name &key age (gender "unknown"))
  "创建一个人,年龄和性别是可选的"
  (list name age gender))

(make-person "Alice")                         ; 返回 ("Alice" NIL "unknown")
(make-person "Bob" :age 25)                  ; 返回 ("Bob" 25 "unknown")
(make-person "Charlie" :age 30 :gender "male") ; 返回 ("Charlie" 30 "male")

多返回值

Common Lisp支持函数返回多个值,使用values函数:

(defun divide (a b)
  "返回除法的商和余数"
  (values (floor a b) (mod a b)))

;; 调用函数
(divide 10 3)  ; 返回 3 和 1

;; 使用multiple-value-bind获取多个返回值
(multiple-value-bind (quotient remainder)
    (divide 10 3)
  (format t "商: ~a, 余数: ~a~%" quotient remainder))
  ; 输出: 商: 3, 余数: 1

高阶函数

在Common Lisp中,函数可以作为参数传递给其他函数,也可以作为返回值。使用function或简写#'获取函数对象:

(defun apply-twice (fn x)
  "将函数fn应用到x两次"
  (funcall fn (funcall fn x)))

;; 定义一个平方函数
(defun square (x) (* x x))

;; 将square函数作为参数传递
(apply-twice #'square 3)  ; 返回 81 (3的平方的平方)

匿名函数

使用lambda创建匿名函数:

(funcall (lambda (x) (* x x)) 5)  ; 返回 25

;; 与高阶函数结合使用
(mapcar (lambda (x) (* x x)) '(1 2 3 4 5))  ; 返回 (1 4 9 16 25)

5. 列表操作

列表是Lisp中最基本的数据结构,Lisp这个名字本身就来源于"LISt Processing"(列表处理)。Common Lisp提供了丰富的列表操作函数。

基本列表操作

cons函数

cons函数用于构造列表,它接受两个参数:一个元素和一个列表,返回一个新列表,其中第一个参数是列表的第一个元素,第二个参数是列表的剩余部分。

(cons 'a '(b c d))  ; 返回 (A B C D)
(cons 'a '())        ; 返回 (A)
(cons 'a nil)        ; 返回 (A)

car和cdr函数

car函数返回列表的第一个元素,cdr函数返回除第一个元素外的剩余部分。

(car '(a b c d))  ; 返回 A
(cdr '(a b c d))  ; 返回 (B C D)

注意:carcdr的名称来源于历史原因,分别是"Contents of the Address part of the Register"和"Contents of the Decrement part of the Register"的缩写。现代Lisp实现中也提供了更直观的别名:first(对应car)和rest(对应cdr)。

组合使用car和cdr

可以组合使用carcdr,形成如cadrcddr等函数:

(cadr '(a b c d))  ; 等同于 (car (cdr '(a b c d))),返回 B
(cddr '(a b c d))  ; 等同于 (cdr (cdr '(a b c d))),返回 (C D)
(caddr '(a b c d)) ; 等同于 (car (cdr (cdr '(a b c d)))),返回 C

列表构造函数

list函数

list函数接受任意数量的参数,返回一个包含这些参数的列表:

(list 'a 'b 'c 'd)  ; 返回 (A B C D)
(list 1 2 3 4)      ; 返回 (1 2 3 4)

append函数

append函数将多个列表连接成一个列表:

(append '(a b) '(c d))     ; 返回 (A B C D)
(append '(a b) '(c d) '(e)) ; 返回 (A B C D E)

列表访问函数

nth函数

nth函数访问列表中的第n个元素(从0开始计数):

(nth 0 '(a b c d))  ; 返回 A
(nth 2 '(a b c d))  ; 返回 C

first, second, third等函数

Common Lisp提供了一系列函数来访问列表的前几个元素:

(first '(a b c d))   ; 返回 A
(second '(a b c d))  ; 返回 B
(third '(a b c d))   ; 返回 C
(fourth '(a b c d))  ; 返回 D

列表映射函数

mapcar函数

mapcar函数将一个函数应用到列表的每个元素,并返回结果列表:

(mapcar #'(lambda (x) (* x x)) '(1 2 3 4 5))  ; 返回 (1 4 9 16 25)
(mapcar #'list '(a b c) '(1 2 3))              ; 返回 ((A 1) (B 2) (C 3))

maplist函数

maplist函数类似于mapcar,但它将函数应用到列表的连续cdr上:

(maplist #'(lambda (x) x) '(a b c))  ; 返回 ((A B C) (B C) (C))

列表搜索函数

member函数

member函数在列表中搜索一个元素,如果找到,返回从该元素开始的子列表;否则返回nil

(member 'b '(a b c d))  ; 返回 (B C D)
(member 'e '(a b c d))  ; 返回 NIL

find函数

find函数在列表中搜索满足条件的元素,如果找到,返回该元素;否则返回nil

(find 'b '(a b c d))           ; 返回 B
(find-if #'evenp '(1 2 3 4 5)) ; 返回 2

列表过滤函数

remove-if函数

remove-if函数移除列表中满足条件的元素:

(remove-if #'evenp '(1 2 3 4 5))  ; 返回 (1 3 5)

remove-if-not函数

remove-if-not函数保留列表中满足条件的元素:

(remove-if-not #'evenp '(1 2 3 4 5))  ; 返回 (2 4)

列表归约函数

reduce函数

reduce函数将一个二元函数应用到列表的元素上,从左到右归约列表:

(reduce #'+ '(1 2 3 4 5))  ; 返回 15 (1+2+3+4+5)
(reduce #'* '(1 2 3 4 5))  ; 返回 120 (1*2*3*4*5)

6. 简单示例代码

本节提供一些简单的Common Lisp代码示例,帮助理解前面介绍的概念。

阶乘计算

;; 递归实现阶乘
(defun factorial (n)
  "计算n的阶乘"
  (if (<= n 1)
      1
      (* n (factorial (- n 1)))))

;; 调用函数
(factorial 5)  ; 返回 120

斐波那契数列

;; 递归实现斐波那契数列
(defun fibonacci (n)
  "计算第n个斐波那契数"
  (cond ((= n 0) 0)
        ((= n 1) 1)
        (t (+ (fibonacci (- n 1))
              (fibonacci (- n 2))))))

;; 调用函数
(fibonacci 10)  ; 返回 55

列表操作示例

;; 计算列表中所有偶数的和
(defun sum-even (lst)
  "计算列表中所有偶数的和"
  (reduce #'+ (remove-if-not #'evenp lst)))

;; 调用函数
(sum-even '(1 2 3 4 5 6 7 8 9 10))  ; 返回 30

;; 获取列表中的最大值
(defun my-max (lst)
  "获取列表中的最大值"
  (reduce #'(lambda (a b) (if (> a b) a b)) lst))

;; 调用函数
(my-max '(3 7 2 9 5))  ; 返回 9

高阶函数示例

;; 实现map函数
(defun my-map (fn lst)
  "自定义map函数"
  (if (null lst)
      '()
      (cons (funcall fn (first lst))
            (my-map fn (rest lst)))))

;; 调用函数
(my-map #'(lambda (x) (* x x)) '(1 2 3 4 5))  ; 返回 (1 4 9 16 25)

;; 实现filter函数
(defun my-filter (fn lst)
  "自定义filter函数"
  (cond ((null lst) '())
        ((funcall fn (first lst))
         (cons (first lst) (my-filter fn (rest lst))))
        (t (my-filter fn (rest lst)))))

;; 调用函数
(my-filter #'evenp '(1 2 3 4 5 6 7 8 9 10))  ; 返回 (2 4 6 8 10)

简单的学生信息管理系统

;; 定义学生结构
(defstruct student
  name
  age
  grade)

;; 创建学生列表
(defparameter *students*
  (list (make-student :name "Alice" :age 20 :grade 90)
        (make-student :name "Bob" :age 21 :grade 85)
        (make-student :name "Charlie" :age 22 :grade 95)))

;; 按成绩筛选学生
(defun filter-by-grade (min-grade students)
  "筛选成绩大于等于min-grade的学生"
  (remove-if-not #'(lambda (s) (>= (student-grade s) min-grade))
                 students))

;; 调用函数
(filter-by-grade 90 *students*)
; 返回 (#S(STUDENT :NAME "Alice" :AGE 20 :GRADE 90)
;       #S(STUDENT :NAME "Charlie" :AGE 22 :GRADE 95))

;; 计算平均成绩
(defun average-grade (students)
  "计算学生的平均成绩"
  (/ (reduce #'+ (mapcar #'student-grade students))
     (length students)))

;; 调用函数
(average-grade *students*)  ; 返回 90

提示:这些示例只是Common Lisp功能的冰山一角。Common Lisp是一门功能强大的编程语言,支持多种编程范式,包括函数式编程、面向对象编程等。继续探索和学习,你会发现更多有趣和强大的特性。