Guile/Scheme 编程教程 / 第3章:基本语法
第 3 章:基本语法
3.1 S-表达式
S-表达式(Symbolic Expression)是 Lisp/Scheme 程序的基本构建单元。所有 Guile 代码都由 S-表达式组成。
3.1.1 S-表达式的基本形式
┌─ S-表达式的三种基本形式 ─────────────────────────┐
│ │
│ 1. 原子 (atom) : 数字、字符串、符号、布尔 │
│ 2. 列表 (list) : (expr1 expr2 ... exprN) │
│ 3. 点对 (dotted pair): (expr1 . expr2) │
│ │
└────────────────────────────────────────────────────┘
;; 原子 (atoms)
42 ; 数字
3.14 ; 浮点数
"hello" ; 字符串
'foo ; 符号
#t ; 布尔真
#f ; 布尔假
;; 列表 (lists)
(+ 1 2 3) ; 函数调用
(define x 10) ; 特殊形式
'(1 2 3 4) ; 字面列表
;; 点对 (dotted pairs)
(1 . 2) ; 由两个值组成的序对
('a . 42) ; 符号与数字的序对
3.1.2 为什么使用 S-表达式
| 优势 | 说明 |
|---|---|
| 语法极简 | 只需理解括号和原子 |
| 代码即数据 | 代码本身是可以操作的数据结构 |
| 宏系统友好 | 统一的表示使宏编写更简单 |
| 可嵌套 | 表达式可以任意深度嵌套 |
3.2 求值规则
理解 Guile 的求值(evaluation)规则是掌握 Scheme 的关键。
3.2.1 基本求值规则
┌─ 求值规则 ────────────────────────────────────┐
│ │
│ 1. 数字、字符串、布尔 → 求值为自身 │
│ 2. 符号 → 查找当前环境中的绑定值 │
│ 3. 列表 → 检查第一个元素: │
│ a. 特殊形式 → 按特殊规则求值 │
│ b. 函数/过程 → 先求值所有参数,再调用函数 │
│ │
└────────────────────────────────────────────────┘
;; 规则 1: 原子求值为自身
42 ; => 42
3.14 ; => 3.14
"hello" ; => "hello"
#t ; => #t
;; 规则 2: 符号查找绑定
(define x 10)
x ; => 10(查找 x 的绑定)
;; 规则 3a: 特殊形式
;; define, if, cond, let, lambda, quote 等都是特殊形式
(define y 20) ; define 不会先求值 y
(if #t 1 2) ; if 不会先求值 then 和 else
;; 规则 3b: 函数调用
(+ 1 2 3)
;; 1. 求值 + → 得到加法过程
;; 2. 求值 1 → 1
;; 3. 求值 2 → 2
;; 4. 求值 3 → 3
;; 5. 调用 (+ 1 2 3) → 6
;; 复杂嵌套
(+ (* 3 4) (- 10 5))
;; 1. 求值 + → 加法过程
;; 2. 求值 (* 3 4) → 12
;; 3. 求值 (- 10 5) → 5
;; 4. 调用 (+ 12 5) → 17
3.2.2 特殊形式一览
| 特殊形式 | 功能 | 求值规则 |
|---|---|---|
define | 定义变量/函数 | 不求值第一个参数(名称) |
if | 条件分支 | 求值条件,选择性求值分支 |
cond | 多条件分支 | 逐个求值条件 |
let / let* | 局部绑定 | 先求值所有值表达式 |
lambda | 创建函数 | 不求值参数列表 |
quote / ' | 引用 | 不求值参数,返回字面数据 |
set! | 修改绑定 | 求值新值,绑定不变量名 |
begin | 顺序执行 | 依次求值所有子表达式 |
and / or | 逻辑运算 | 短路求值 |
case | 值匹配 | 求值键值后匹配 |
3.2.3 quote 与 quasiquote
;; quote: 阻止求值
(quote (+ 1 2)) ; => (+ 1 2)(不求值,返回列表)
'(+ 1 2) ; => (+ 1 2)(简写形式)
;; 没有 quote 会怎样?
;; (+ 1 2) → 3(作为函数调用求值了)
;; quasiquote: 模板(类似模板字符串)
;; ` 启用准引用,, 解引用(取消准引用)
(define name "Guile")
`(hello ,name) ; => (hello "Guile")
`(1 ,(+ 2 3) 4) ; => (1 5 4)
;; ,@ 展开列表
(define xs '(2 3 4))
`(1 ,@xs 5) ; => (1 2 3 4 5)
;; 对比 quote 和 quasiquote
(define x 10)
'(+ x 1) ; => (+ x 1) — x 未被求值
`(+ ,x 1) ; => (+ 10 1) — x 被求值后插入
3.3 注释
;; 单行注释 —— 使用分号
;; 这是标准的行注释
;; 不同数量的分号有约定含义:
;;;; 文件头部注释(4个分号)
;;; 章节标题注释(3个分号)
;; 段落注释(2个分号,最常用)
; 行尾注释(1个分号)
;; 表达式注释 —— 使用 #; 注释掉下一个完整表达式
#;(+ 1 2) ; 这个表达式被完全跳过
(+ 3 4) ; 这个会正常求值 → 7
;; 多行注释 —— 使用 #| 和 |#
#|
这是多行注释
可以跨越多行
嵌套也行:#| 内层 |# 外层
|#
;; 文档注释 —— 使用三个分号
;;; greet — 生成问候语
;;; 参数 name: 用户名
;;; 返回: 问候字符串
(define (greet name)
(string-append "Hello, " name "!"))
注意:Guile 中没有像 Python 的 docstring 那样的原生文档字符串机制。文档通过注释和 Texinfo 手册来编写。
3.4 数据类型总览
Guile 是动态类型语言,所有值都有明确的类型。
3.4.1 类型层次
┌──────────┐
│ Scheme │
│ Value │
└────┬─────┘
┌─────────────┼─────────────┐
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 原子 │ │ 复合 │ │ 过程 │
│ Atomic │ │ Compound│ │Procedure│
└────┬────┘ └────┬────┘ └─────────┘
│ │
┌──────┼──────┐ ┌────┼────┐
│ │ │ │ │ │
数字 字符串 符号 列表 向量 哈希表
3.4.2 类型检查函数
;; Guile 提供了丰富的类型检查谓词
(number? 42) ; => #t
(string? "hello") ; => #t
(symbol? 'foo) ; => #t
(list? '(1 2 3)) ; => #t
(pair? '(1 . 2)) ; => #t
(vector? #(1 2 3)) ; => #t
(boolean? #t) ; => #t
(char? #\a) ; => #t
(procedure? +) ; => #t
(null? '()) ; => #t
(integer? 42) ; => #t
(real? 3.14) ; => #t
(exact? 42) ; => #t
(inexact? 3.14) ; => #t(通常)
(inexact? 1.0) ; => #t
;; 通用类型检查
(equal? 1 1) ; => #t
(eq? 'a 'a) ; => #t(同一对象)
(eqv? 1 1) ; => #t(数值相等)
3.5 数字类型
Guile 支持丰富的数字类型,完全符合 Scheme 标准。
3.5.1 数字类型概览
| 类型 | 示例 | 说明 |
|---|---|---|
| 整数 | 42, -7, 0 | 精确整数,任意精度 |
| 有理数 | 1/3, 22/7 | 精确分数 |
| 浮点数 | 3.14, -1.5e10 | 不精确实数 |
| 复数 | 1+2i, 3-4i | 直角坐标 |
| 复数(极坐标) | [email protected] | 极坐标形式 |
;; 整数 —— 任意精度(大整数)
(exact? 42) ; => #t
(exact? 123456789012345678901234567890) ; => #t
(* 123456789 987654321) ; => 121932631112635269(精确结果)
;; 有理数
(/ 1 3) ; => 1/3(精确!)
(exact? 1/3) ; => #t
(+ 1/3 1/6) ; => 1/2
(* 22/7 7) ; => 22
;; 浮点数
3.14 ; => 3.14
1.0e-10 ; => 1.0e-10
(inexact? 3.14) ; => #t
;; 注意精确与不精确的区别
(+ 1/3 2/3) ; => 1(精确)
(+ 0.333 0.666) ; => 0.999(不精确)
;; 复数
1+2i ; => 1+2i
(make-rectangular 3 4) ; => 3+4i
(make-polar 5 0.9273) ; => 约 3+4i
(real-part 3+4i) ; => 3
(imag-part 3+4i) ; => 4
(magnitude 3+4i) ; => 5
(angle 3+4i) ; => 约 0.9273
3.5.2 数字进制
;; 不同进制的数字表示
#b1010 ; => 10(二进制)
#o777 ; => 511(八进制)
#xFF ; => 255(十六进制)
#d42 ; => 42(十进制,默认)
;; 以字符串形式输出不同进制
(number->string 255 2) ; => "11111111"
(number->string 255 8) ; => "377"
(number->string 255 16) ; => "ff"
;; 从字符串解析
(string->number "ff" 16) ; => 255
(string->number "1010" 2) ; => 10
(string->number "777" 8) ; => 511
(string->number "42") ; => 42
3.5.3 常用数学运算
;; 基本运算
(+ 1 2 3 4) ; => 10(加法,支持多参数)
(- 10 3) ; => 7(减法)
(* 2 3 4) ; => 24(乘法)
(/ 10 3) ; => 10/3(精确除法)
(quotient 17 5) ; => 3(整数商)
(remainder 17 5) ; => 2(余数)
(modulo 17 5) ; => 2(模运算)
(abs -5) ; => 5(绝对值)
;; 比较
(= 1 1) ; => #t(数值相等)
(< 1 2) ; => #t
(> 3 2) ; => #t
(<= 1 1) ; => #t
(>= 5 3) ; => #t
(max 1 2 3) ; => 3
(min 1 2 3) ; => 1
;; 数学函数
(sqrt 16) ; => 4
(expt 2 10) ; => 1024
(exp 1) ; => 2.718281828...
(log (exp 1)) ; => 1.0
(sin 0) ; => 0
(cos 0) ; => 1
(tan 0) ; => 0
;; 取整
(round 3.5) ; => 4.0
(floor 3.7) ; => 3.0
(ceiling 3.2) ; => 4.0
(truncate 3.9) ; => 3.0
;; 数论
(gcd 12 8) ; => 4(最大公约数)
(lcm 3 4) ; => 12(最小公倍数)
(even? 4) ; => #t
(odd? 3) ; => #t
(zero? 0) ; => #t
(positive? 5) ; => #t
(negative? -3) ; => #t
3.5.4 数字精度转换
;; 精确 ↔ 不精确转换
(exact->inexact 1/3) ; => 0.3333333333333333
(inexact->exact 0.333) ; => 333/1000
;; 更简短的方式
(exact 1.5) ; => 3/2
(inexact 3/4) ; => 0.75
;; 指定精度输出
(number->string 1/3 10) ; => "1/3"
3.6 字符串
3.6.1 字符串创建
;; 字面量
"Hello, World!" ; 基本字符串
"包含中文的字符串" ; Unicode 支持
"He said \"Hi\"" ; 转义引号
"line1\nline2" ; 换行符
"tab\there" ; 制表符
"backslash: \\" ; 反斜杠
;; 构造函数
(make-string 5 #\x) ; => "xxxxx"
(make-string 3 #\space) ; => " "
;; 字符串复制
(string-copy "Hello") ; => "Hello"(新副本)
3.6.2 字符串操作
;; 基本操作
(string-length "Hello") ; => 5
(string-ref "Hello" 0) ; => #\H(返回字符)
(string-ref "Hello" 4) ; => #\o
;; 拼接
(string-append "Hello" ", " "World!") ; => "Hello, World!"
(string-append "Guile" " " (number->string 3)) ; => "Guile 3"
;; 比较
(string=? "abc" "abc") ; => #t(相等)
(string<? "abc" "abd") ; => #t(字典序小于)
(string>? "b" "a") ; => #t
(string-ci=? "ABC" "abc") ; => #t(大小写不敏感)
;; 子串
(substring "Hello World" 0 5) ; => "Hello"
(substring "Hello World" 6) ; => "World"
;; 搜索
(string-contains "Hello Guile" "Guile") ; => 6(位置)
(string-contains "Hello" "xyz") ; => #f(未找到)
;; 查找字符
(string-index "Hello" #\l) ; => 2
(string-rindex "Hello" #\l) ; => 3
;; 转换
(string-upcase "hello") ; => "HELLO"
(string-downcase "HELLO") ; => "hello"
(string-titlecase "hello world") ; => "Hello World"
(string-reverse "abc") ; => "cba"
;; 去空白
(string-trim-both " hello ") ; => "hello"
(string-trim " hello ") ; => "hello "
(string-trim-right " hello ") ; => " hello"
;; 替换
(string-map char-upcase "hello") ; => "HELLO"
注意:字符串操作大多来自 SRFI-13。使用前可能需要
(use-modules (srfi srfi-13)),但 Guile 已经自动加载了大部分 SRFI-13 函数。
3.6.3 字符串与列表转换
;; 字符串 → 字符列表
(string->list "abc") ; => (#\a #\b #\c)
;; 字符列表 → 字符串
(list->string '(#\H #\i)) ; => "Hi"
;; 字符串 → 数字
(string->number "42") ; => 42
(string->number "3.14") ; => 3.14
;; 数字 → 字符串
(number->string 42) ; => "42"
(number->string 3.14) ; => "3.14"
(number->string 255 16) ; => "ff"(十六进制)
;; 字符串分割(SRFI-13)
(string-split "a,b,c" #\,) ; => ("a" "b" "c")
(string-split "hello world" #\space) ; => ("hello" "world")
;; 字符串连接
(string-join '("a" "b" "c") ", ") ; => "a, b, c"
(string-join '("line1" "line2") "\n") ; => "line1\nline2"
3.6.4 格式化输出
;; format —— 类似 C 的 printf
(format #t "Hello, ~a!~%" "World")
;; 输出: Hello, World!(带换行)
;; #t 表示输出到当前输出端口
;; format 返回字符串(第一个参数为 #f)
(format #f "Result: ~a" 42)
;; => "Result: 42"
;; 格式化指令
;; ~a — 显示(人类可读)
;; ~s — S-表达式(可被 read 读取)
;; ~d — 十进制整数
;; ~x — 十六进制
;; ~o — 八进制
;; ~b — 二进制
;; ~f — 浮点数
;; ~% — 换行
;; ~~ — 波浪号
(format #f "~a" "Hello") ; => "Hello"
(format #f "~s" "Hello") ; => "\"Hello\""
(format #f "~d" 42) ; => "42"
(format #f "~8,'0d" 42) ; => "00000042"(8位补零)
(format #f "~x" 255) ; => "ff"
(format #f "~b" 10) ; => "1010"
(format #f "~,2f" 3.14159) ; => "3.14"(2位小数)
(format #f "~10a" "Hi") ; => "Hi "(右填充)
(format #f "~10@a" "Hi") ; => " Hi"(左填充)
;; 条件格式化
(format #f "~[zero~;one~;two~;three~]" 2) ; => "two"
3.7 字符
;; 字符字面量
#\a ; 字母 a
#\A ; 大写 A
#\0 ; 数字字符 0
#\space ; 空格
#\newline ; 换行
#\tab ; 制表符
#\null ; 空字符
;; 字符操作
(char->integer #\a) ; => 97(ASCII/Unicode 码点)
(integer->char 97) ; => #\a
(char-upcase #\a) ; => #\A
(char-downcase #\A) ; => #\a
(char-alphabetic? #\a) ; => #t
(char-numeric? #\0) ; => #t
(char-whitespace? #\space) ; => #t
;; 字符比较
(char=? #\a #\a) ; => #t
(char<? #\a #\b) ; => #t
(char-ci=? #\A #\a) ; => #t(大小写不敏感)
3.8 布尔值与逻辑
;; 真值规则:除 #f 外,所有值都是真
#t ; 真
#f ; 假
0 ; 真!(不是假)
"" ; 真!
'() ; 真!(不是假)
#f ; 唯一的假值
;; 逻辑运算
(and #t #t) ; => #t
(and #t #f) ; => #f
(and 1 2 3) ; => 3(返回最后一个真值)
(and) ; => #t
(or #f #f) ; => #f
(or #t #f) ; => #t
(or #f 2 3) ; => 2(返回第一个真值)
(or) ; => #f
(not #t) ; => #f
(not #f) ; => #t
(not 0) ; => #f(0 是真值)
;; 短路求值
(and #f (error "不会执行")) ; => #f
(or #t (error "不会执行")) ; => #t
;; 谓词 —— Scheme 中返回布尔值的函数通常以 ? 结尾
(null? '()) ; => #t
(pair? '(1 . 2)) ; => #t
(list? '(1 2 3)) ; => #t
(zero? 0) ; => #t
(positive? 1) ; => #t
(negative? -1) ; => #t
(even? 2) ; => #t
(odd? 3) ; => #t
3.9 符号
符号(Symbol)是 Lisp/Scheme 中独特的数据类型,用作标识符和关键字。
;; 创建符号
'hello ; 引用创建符号
(string->symbol "hello") ; 从字符串创建
;; 符号是唯一的
(eq? 'hello 'hello) ; => #t(同一符号)
(eq? 'hello 'world) ; => #f
;; 符号 → 字符串
(symbol->string 'hello) ; => "hello"
;; gensym —— 生成唯一符号(宏中常用)
(gensym) ; => #:G123(保证不重复)
;; 用符号作为关联列表的键
(define config '((host . "localhost")
(port . 8080)
(debug . #t)))
(assq config 'port) ; => (port . 8080)
3.10 本章小结
| 主题 | 要点 |
|---|---|
| S-表达式 | 原子、列表、点对三种形式 |
| 求值规则 | 原子自求值,符号查绑定,列表看首位 |
| quote/quasiquote | 阻止求值 / 模板求值 |
| 数字 | 精确整数/有理数、不精确浮点、复数 |
| 字符串 | 完整的 Unicode 支持,SRFI-13 操作 |
| 布尔 | #f 为假,其余皆为真 |
扩展阅读
上一章:第 2 章:安装与环境搭建 下一章:第 4 章:列表与序对