Scheme 类型系统:类型谓词
Scheme 是一种动态类型的函数式语言,这意味着变量在运行时才确定其类型。尽管编译器不会在编译期进行类型检查,但 Scheme 提供了一套完善的类型谓词机制,让程序员能够在运行时判断一个值的类型。理解这些类型谓词,是编写健壮 Scheme 程序的基础。
什么是类型谓词
类型谓词(Type Predicate)是 Scheme 中一类特殊的函数,它们的名字通常以问号 ? 结尾。这些函数的返回值很简单:若参数符合某种类型条件,则返回 #t(真);否则返回 #f(假)。
;; 判断 42 是否为数字
(number? 42) ; => #t
;; 判断 "hello" 是否为数字
(number? "hello") ; => #f
;; 判断列表是否为空
(null? '()) ; => #t
(null? '(1 2 3)) ; => #f
类型谓词的核心作用在于让程序在运行时自我检查。当你编写一个函数时,可以利用类型谓词验证传入参数的合法性,从而在类型错误发生之前捕获它们。
基础类型谓词
Scheme 定义了一组核心类型谓词,用于识别最基本的数据类型。以下是日常编程中最常用的几个:
数值类型判断
数值类型在 Scheme 中细分为多种形式。number? 检查值是否为任意形式的数值,包括整数、浮点数、有理数和复数。
(number? 42) ; => #t
(number? 3.14) ; => #t
(number? #i3.14) ; => #t(区间数)
(number? 3+4i) ; => #t(复数)
如果需要更精细的判断,可以使用以下谓词:
| 谓词 | 作用 | 示例 |
|---|---|---|
integer? |
判断是否为整数 | (integer? 42) → #t |
real? |
判断是否为实数 | (real? 3.14) → #t |
rational? |
判断是否为有理数 | (rational? 22/7) → #t |
complex? |
判断是否为复数 | (complex? 3+4i) → #t |
exact? |
判断是否为精确数 | (exact? 22/7) → #t |
inexact? |
判断是否为非精确数 | (inexact? 3.14) → #t |
序对与列表
Scheme 的序对(Pair)是构建链表的基本单元,cons 创建序对,car 取首元素,cdr 取剩余部分。
;; 序对判断
(pair? (cons 1 2)) ; => #t
(pair? '(1 . 2)) ; => #t
(pair? '(1 2 3)) ; => #t(列表本质上是一系列序对)
(pair? "hello") ; => #f
列表是序对的特例:要么是空列表 '(),要么是以序对形式链式连接的结构。
;; 列表判断
(list? '()) ; => #t
(list? '(1 2 3)) ; => #t
(list? (cons 1 2)) ; => #f(点序对不是列表)
符号与布尔
符号(Symbol)是 Scheme 中表示标识符的类型,常用于枚举和键值查找。
(symbol? 'hello) ; => #t
(symbol? "hello") ; => #f(字符串不是符号)
(symbol? 123) ; => #f
布尔值只有两个:#t 和 #f。值得注意的是,#t 表示真,#f 表示假。在条件判断中,除了 #f 之外的所有值都被视为真。
(boolean? #t) ; => #t
(boolean? #f) ; => #t
(boolean? 0) ; => #f
(boolean? "true") ; => #f
字符与字符串
字符表示单个 Unicode 字符,使用 #\ 前缀表示。
(char? #\a) ; => #t
(char? "a") ; #f
(char? 97) ; #f
字符串是字符的序列:
(string? "hello") ; => #t
(string? 'hello) ; => #f(符号不是字符串)
相等性判断
Scheme 提供了多个相等性谓词,它们判断"相等"的角度各有不同。选择正确的相等性谓词,是避免微妙 bug 的关键。
eq? 与 eqv?
eq? 比较两个对象的内存地址,是最严格、最快速但也最受限的相等判断。它只保证:相同的符号指向同一内存位置;相同的布尔常量是同一对象。
(eq? 'a 'a) ; => #t(符号是 interned)
(eq? "hello" "hello") ; => 可能 #f(字符串可能不是同一对象)
(eq? '(1 2) '(1 2)) ; => 可能 #f(不同的列表对象)
eqv? 在 eq? 的基础上增加了对数值和字符的合理比较:相同的数字值被视为相等,无论它们是否是同一对象。
(eqv? 42 42) ; => #t
(eqv? 3.14 3.14) ; => #t
(eqv? #\a #\a) ; => #t
(eqv? '(1 2) '(1 2)) ; => 可能 #f(同上)
equal?
`equal? 递归地比较对象的结构内容。它判断两个对象是否具有相同的结构和值,适用于列表、向量、字符串等复合类型。
(equal? '(1 2 3) '(1 2 3)) ; => #t
(equal? "hello" "hello") ; => #t
(equal? '#(1 2) '#(1 2)) ; => #t
(equal? '(1 (2 3)) '(1 (2 3))) ; => #t
下表总结了三个相等性谓词的适用场景:
| 谓词 | 速度 | 适用类型 | 典型用途 |
|---|---|---|---|
eq? |
最快 | 符号、布尔、关键字 | 标识符比较 |
eqv? |
快 | 数字、字符、符号 | 数值比较 |
equal? |
较慢 | 列表、向量、字符串 | 结构比较 |
过程与向量
`procedure? 判断一个值是否为过程(函数),这是在高阶函数编程中进行参数验证的重要工具。
(define (add x y) (+ x y))
(procedure? add) ; => #t
(procedure? +) ; => #t(内置过程也是过程)
(procedure? 42) ; => #f
向量是 Scheme 中另一种复合类型,使用括号加 # 前缀表示。与列表不同,向量是扁平存储的随机访问结构。
(vector? '#(1 2 3)) ; => #t
(vector? '(1 2 3)) ; => #f(列表不是向量)
(vector-length '#(1 2 3)) ; => 3
类型谓词的实际应用
理解类型谓词的语法只是第一步,更重要的是知道如何在实际编程中运用它们。
参数验证
编写函数时,使用类型谓词验证输入是良好的防御性编程习惯。
(define (factorial n)
(cond
((not (integer? n)))
((negative? n))
(else ...)))
上面的代码检查 n 是否为非负整数。如果不是,函数可以选择报错或返回特定值。
条件分支
类型谓词常与 cond 表达式结合,实现基于类型的分支逻辑。
(define (describe x)
(cond
((number? x) "这是一个数字")
((string? x) "这是一个字符串")
((pair? x) "这是一个序对")
((symbol? x) "这是一个符号")
(else "未知类型")))
泛型函数
类型谓词是实现泛型函数的基础。通过检查参数类型,函数可以针对不同类型执行不同的操作。
(define (size x)
(cond
((string? x) (string-length x))
((vector? x) (vector-length x))
((pair? x) (length x)) ; 假设 length 已定义
(else 1)))
端口与特殊类型
Scheme 还提供了用于输入输出的端口类型谓词:
(input-port? (current-input-port)) ; => #t
(output-port? (current-output-port)) ; => #t
以及用于标识符唯一性的 keyword?:
(define #:hello "world")
(keyword? #:hello) ; => #t
这些特殊类型谓词在处理元编程和 I/O 操作时非常有用。
编写自定义类型谓词
Scheme 允许你定义自己的类型谓词。通常,自定义谓词基于现有谓词的组合或特定的结构检查。
;; 判断是否为非空列表
(define (non-empty-list? x)
(and (pair? x) (list? x)))
;; 判断是否为正整数
(define (positive-integer? x)
(and (integer? x) (>= x 0) (not (zero? x))))
通过组合基础谓词,你可以精确地定义程序所需的类型约束。
总结
Scheme 的类型谓词系统为动态类型语言提供了运行时的类型检查能力。从基础的 number?、string? 到精细的 eq?、equal?,这些谓词构成了 Scheme 程序的类型安全网。掌握它们的区别和使用场景,能够帮助你写出更加健壮、可维护的代码。记住:动态类型不意味着放弃类型安全——类型谓词正是你在运行时守护代码的正确工具。

暂无评论,快来抢沙发吧!