Skip to content

LISP - 数据类型

Common Lisp 中一个核心概念是:变量是无类型的(untyped),但值是有类型的(typed)。这意味着任何变量都可以存储任何类型的数据,但系统中的每份数据(或称对象)都知晓自身的类型。这使得 Lisp 兼具动态语言的灵活性和丰富而强大的类型系统。

Lisp 中的所有对象都属于一个类型层级结构。该层级结构的根是 T,它代表所有类型的父类型(或称超类型)。层级结构的底部是 NIL,这是一个没有成员的类型。Common Lisp 提供了丰富的内置类型。

以下是一些最基本、最常用的类型说明符:

  • 数字(Numbers): number, integer (整数), fixnum (定长整数), bignum (大整数), ratio (有理数), float (浮点数), complex (复数)
  • 字符(Characters): character (字符), standard-char (标准字符)
  • 符号(Symbols): symbol (符号), keyword (关键字), null (对象 nil 的类型)
  • Cons 单元格和列表(Cons Cells and Lists): cons (构造函数), list (列表), null (空列表的类型)
  • 数组(Arrays): array (数组), vector (向量), simple-vector (简单向量), string (字符串), simple-string (简单字符串), bit-vector (位向量)
  • 序列(Sequences): sequence (序列,是 list 和 vector 的超类型)
  • 函数(Functions): function (函数), compiled-function (编译函数)
  • 结构体和对象(Structures and Objects): structure-object (结构体对象), standard-object (标准对象,来自 CLOS)

Lisp 提供了两个用于处理类型的主要函数:

  • type-of object: 返回 object 类型的类型说明符。结果可能依赖于具体的实现,因此它最适用于交互式调试。
  • typep object type-specifier: 这是一个谓词(predicate),如果 object 属于指定的类型,则返回 T;否则返回 nil。这是检查对象类型的推荐且可移植的方式。
(let ((my-integer 42)
(my-float 3.14)
(my-string "hello")
(my-list '(a b c)))
;; --- 使用 type-of(用于调试)---
(format t "Type of my-integer: ~s~%" (type-of my-integer)) ; 可能是 FIXNUM 或 (UNSIGNED-BYTE 62)
(format t "Type of my-string: ~s~%" (type-of my-string)) ; 可能是 (SIMPLE-ARRAY CHARACTER (5))
;; --- 使用 typep(用于健壮的类型检查)---
(format t "Is my-integer an INTEGER? ~a~%" (typep my-integer 'integer))
(format t "Is my-float a NUMBER? ~a~%" (typep my-float 'number))
(format t "Is my-string a LIST? ~a~%" (typep my-string 'list))
(format t "Is my-list a SEQUENCE? ~a~%" (typep my-list 'sequence)))
Type of my-integer: (UNSIGNED-BYTE 62)
Type of my-string: (SIMPLE-ARRAY CHARACTER (5))
Is my-integer an INTEGER? T
Is my-float a NUMBER? T
Is my-string a LIST? NIL
Is my-list a SEQUENCE? T

尽管 Lisp 是动态类型的,但你可以提供可选的类型声明。这些声明主要有两个目的:

  • 性能: 它们向编译器提供提示,通过避免泛型操作和运行时类型检查,从而生成更专业化、更快的机器代码。
  • 错误检查: 现代编译器可以使用这些声明在编译时警告你是否向函数传递了错误类型的数据。
;; 此函数用于计算圆的面积。
;; 我们声明半径必须是浮点数。
(defun circle-area (radius)
(declare (type float radius))
(declare (optimize (speed 3) (safety 0))) ; 要求编译器优先考虑速度
(* pi radius radius))
;; 使用正确类型调用它,既能工作又速度快。
(print (circle-area 10.0))
;; 使用错误类型调用它可能会发出编译时警告或运行时错误,
;; 具体取决于实现和安全设置。
;; (circle-area 10) ; 这可能会被标记出来。
314.15927