15.4.2 复合类型

当所有简单类型都不适用时,你可以使用复合类型——这类类型基于其他类型或指定数据 构建新类型。被指定的类型或数据称为复合类型的参数(arguments)。复合类型的标准格式如下:

(constructor arguments...)

你也可以在参数前添加“关键字-值”对,格式如下:

(constructor {keyword value}... arguments...)

下表列出了各类构造器,以及如何使用它们定义复合类型:

(cons car-type cdr-type)

值必须是一个 cons 单元(cons cell),其 CAR 部分需符合 car-type 类型, CDR 部分需符合 cdr-type 类型。例如,(cons string symbol) 是一种自定义类型,可匹配 ("foo" . foo) 这类值。

在自定义缓冲区中,CARCDR 会分别显示和编辑, 各自按照其指定的类型进行处理。

(list element-types…)

值必须是一个列表,其元素个数与给出的 element-types 数量完全一致; 并且每个元素必须符合对应的 element-type 类型。

例如,(list integer string function) 描述了一个包含三个元素的列表; 第一个元素必须是整数,第二个是字符串,第三个是函数。

在自定义缓冲区中,每个元素会根据为其指定的类型分别显示和编辑。 The value must be a list with exactly as many elements as the

(group element-types…)

用法与 list 类似,区别仅在于自定义缓冲区中文本的显示格式。 list 会为每个元素值加上标签;而 group 不会。

(vector element-types…)

list 类似,区别是值必须是向量(vector)而不是列表。 元素的用法与 list 中相同。

(alist :key-type key-type :value-type value-type)

值必须是一个由 cons 单元组成的列表,每个单元的 CAR 表示一个键, 其自定义类型为 key-type;同一个单元的 CDR 表示一个值, 其自定义类型为 value-type。 用户可以添加和删除键/值对,并编辑每一对的键和值。

如果省略,key-typevalue-type 默认均为 sexp

用户可以添加任何符合指定键类型的键,但你可以通过 :options 关键字指定某些键,使其得到优先显示(see 定义自定义变量)。 这些指定的键会始终显示在自定义缓冲区中(并附带合适的值), 并带有一个复选框,用于在关联列表中启用、禁用或排除该键/值对。 用户无法编辑由 :options 关键字参数指定的键。

:options 关键字的参数应该是一个列表, 列出关联列表中合理的键的说明。通常它们只是原子(atom),表示自身。例如:

:options '("foo" "bar" "baz")

该写法指定了三个已知的键,即 "foo""bar""baz", 这些键会始终优先显示。

你可能希望为特定键限制值类型,例如,与 "bar" 键关联的值只能是整数。 要实现这一点,可将列表中的原子替换为子列表:第一个元素仍像之前一样指定键, 第二个元素则指定该键对应的值类型。例如:

:options '("foo" ("bar" integer) "baz")

最后,你可能希望修改键的展示方式。默认情况下, 由于用户无法修改通过 :options 关键字指定的特殊键, 这些键会以 const 类型直接显示。但你可以为键指定更专用的展示类型—— 比如若已知该键是绑定了函数的符号,可使用 function-item 类型。 实现方式是将键的符号替换为自定义类型规范:

:options '("foo"
           ((function-item some-function) integer)
           "baz")

许多关联列表(alist)会使用双元素列表而非 cons 单元。例如:

(defcustom list-alist
  '(("foo" 1) ("bar" 2) ("baz" 3))
  "Each element is a list of the form (KEY VALUE).")

而非:

(defcustom cons-alist
  '(("foo" . 1) ("bar" . 2) ("baz" . 3))
  "Each element is a cons-cell (KEY . VALUE).")

由于列表是基于 cons 单元实现的,你可以将上述示例中的 list-alist 视作 cons 单元形式的关联列表(alist)——其中值类型是仅包含一个元素的列表, 该元素存储实际值。

(defcustom list-alist '(("foo" 1) ("bar" 2) ("baz" 3))
  "Each element is a list of the form (KEY VALUE)."
  :type '(alist :value-type (group integer)))

此处使用 group 界面组件而非 list,仅因为其显示格式更贴合该场景的需求。

同理,你可以通过这一技巧的变体,实现一个键关联多个值的关联列表:

(defcustom person-data '(("brian"  50 t)
                         ("dorith" 55 nil)
                         ("ken"    52 t))
  "Alist of basic info about people.
Each element has the form (NAME AGE MALE-FLAG)."
  :type '(alist :value-type (group integer boolean)))
(plist :key-type key-type :value-type value-type)

该自定义类型与 alist(见上文)类似,但存在两点区别: (i) 信息以属性列表(property list)的形式存储(see 属性列表); (ii) 若省略 key-type,其默认值为 symbol 而非 sexp

(choice alternative-types…)

值必须符合 alternative-types 中的某一种类型。例如, (choice integer string) 允许值为整数或字符串。

在自定义缓冲区中,用户可通过菜单选择其中一种类型, 随后按照该类型的常规方式编辑对应的值。

通常,此菜单中的字符串由选项自动决定;但你可以在备选类型中使用 :tag 关键字, 为菜单指定不同的显示文字。例如,若一个整数表示空格数量,而字符串表示直接使用的文本, 你可以这样编写自定义类型:

(choice (integer :tag "Number of spaces")
        (string :tag "Literal text"))

这样菜单就会显示 ‘Number of spaces(空格数量)’ 和 ‘Literal text(原文文本)’。

对于 nil 不是有效值、且不是 const 类型的备选选项, 你应当使用 :value 关键字为其指定一个有效的默认值。See 类型关键字

如果某个值可以匹配多个备选类型,自定义系统会选择第一个能匹配该值的类型。 这意味着你应当将最具体的类型放在前面,最通用的类型放在最后。 下面是正确用法示例:

(choice (const :tag "Off" nil)
        symbol (sexp :tag "Other"))

这样一来,特殊值 nil 就不会被当作普通符号处理, 符号也不会被当作普通 Lisp 表达式处理。

(radio element-types…)

choice 类似,区别在于选项以**单选按钮**而非菜单显示。 这样做的好处是,在适用时可以显示选项的说明文档, 因此常用于在多个常量函数(function-item 自定义类型)之间选择。

(const value)

值必须是 value — 不允许其他任何值。

const 的主要用途是用在 choice 内部。例如, (choice integer (const nil)) 允许值为整数或 nil

:tag 经常与 const 配合,用在 choice 内部。 例如:

(choice (const :tag "Yes" t)
        (const :tag "No" nil)
        (const :tag "Ask" foo))

这段代码描述了一个变量:t 表示“是”,nil 表示“否”, foo 表示 “询问(ask)”。

(other value)

该备选选项可以匹配任意 Lisp 值,但如果用户选择此选项, 就会选用值 value

other 的主要用途是作为 choice 的最后一个元素。 例如:

(choice (const :tag "Yes" t)
        (const :tag "No" nil)
        (other :tag "Ask" foo))

这段代码描述了一个变量:t 表示“是”,nil 表示“否”, 其他任何值都表示 “询问(ask)”。 如果用户从选项菜单中选择 ‘询问Ask’,就会设定值为 foo; 而任何其他值(非 t、非 nil、非 foo) 都会像 foo 一样显示为 ‘询问Ask’。

(function-item function)

const 类似,但专用于表示函数类型的值。 它会同时显示函数名和文档字符串。 文档字符串可以是通过 :doc 指定的内容, 也可以是 function 自身自带的文档字符串。

(variable-item variable)

const 类似,但专用于表示变量名类型的值。 它会同时显示变量名和文档字符串。 文档字符串可以是通过 :doc 指定的内容, 也可以是 variable 自身自带的文档字符串。

(set types…)

值必须是一个列表,且列表中的每个元素必须匹配指定的 types 中的某一个。

它在自定义缓冲区中以复选清单的形式显示, 因此每个 types 最多只能对应一个元素,也可以没有。 不允许出现匹配同一类型的多个不同元素。 例如,(set integer symbol) 只允许列表中包含一个整数和/或一个符号; 不允许多个整数或多个符号。因此,在 set 中很少使用 integer 这类不具体的类型。

set 中的 types 绝大多数情况下都是 const 类型,如下例:

(set (const :bold) (const :italic))

有时它们用于描述关联列表(alist)中的可选元素:

(set (cons :tag "Height" (const height) integer)
     (cons :tag "Width" (const width) integer))

这样就可以让用户 可选地 指定高度值和宽度值。

(repeat element-type)

值必须是一个列表,且列表中的每个元素都必须符合 element-type 类型。 它在自定义缓冲区中显示为元素列表,带有 ‘[INS]’ 和 ‘[DEL]’ 按钮, 用于添加或删除元素。

(restricted-sexp :match-alternatives criteria)

这是最通用的复合类型构造器。 值可以是任何满足 criteria 中任一条件的 Lisp 对象。 criteria 必须是一个列表,其中每个元素可以是以下形式之一:

  • 谓词(predicate) — 即单参数函数, 其根据参数返回 nil 或非 nil。 在列表中使用谓词意味着, 谓词返回非 nil 的对象都是可接受的值。
  • 引用常量(quoted constant) — 即 'object。 列表中的此类元素表示,object 本身就是可接受的值。

例如:

(restricted-sexp :match-alternatives
                 (integerp 't 'nil))

该写法允许整数、tnil 作为合法值。

在自定义缓冲区中,所有合法值都会使用其读取语法(read syntax)显示, 用户可以通过文本方式编辑它们。

下面是复合类型中可用于关键字-值对的关键字列表:

:tag tag

tag 用作此备选选项的名称,用于与用户交互。 对于出现在 choice 内部的类型,此关键字非常有用。

:match-alternatives criteria

使用 criteria 匹配可能的值。 该关键字仅在 restricted-sexp 中使用。

:args argument-list

argument-list 的元素用作该类型构造器的参数。 例如,(const :args (foo)) 等价于 (const foo)。 你很少需要显式编写 :args, 因为通常参数会被自动识别为最后一个关键字-值对之后的内容。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike