在前面的章节中,我们介绍了如何为 defcustom 构造复杂的类型规范。
某些情况下,你可能希望给这样的类型规范赋予一个名称。
最典型的场景是:当你为多个用户选项使用 相同类型 时——
不必在每个选项中重复书写类型规范,你可以为类型规范命名,
然后在每个 defcustom 中直接使用该名称。
另一种场景是:用户选项的值是 递归数据结构。
为了让数据类型能够引用自身,它必须拥有名称。
由于自定义类型是通过界面组件(widget)实现的, 定义新自定义类型的方式就是定义一个新组件。 我们不会在这里详细描述组件接口,相关内容请参见 Introduction in The Emacs Widget Library。 我们将通过一个简单示例,演示定义新自定义类型所需的**最小功能集**。
(define-widget 'binary-tree-of-string 'lazy
"A binary tree made of cons-cells and strings."
:offset 4
:tag "Node"
:type '(choice (string :tag "Leaf" :value "")
(cons :tag "Interior"
:value ("" . "")
binary-tree-of-string
binary-tree-of-string)))
(defcustom foo-bar ""
"Sample variable holding a binary tree of strings."
:type 'binary-tree-of-string)
用于定义新组件的函数名为 define-widget。
第一个参数是我们要作为新组件类型的符号;
第二个参数是代表已有组件的符号,新组件将基于与该现有组件的差异来定义。
对于定义新自定义类型的用途来说,lazy 组件非常合适,
因为它接受一个 :type 关键字参数,其语法与同名的 defcustom 关键字参数一致。
第三个参数是新组件的文档字符串,
你可以通过命令 M-x widget-browse RET binary-tree-of-string RET
查看该字符串。
在这些必选参数之后是关键字参数。
其中最重要的是 :type,它描述此组件要匹配的数据类型。
在本例中,binary-tree-of-string 被定义为:
要么是一个字符串,要么是一个 cons 单元,
且其 car 和 cdr 本身又都是 binary-tree-of-string。
注意这里对**正在定义中的组件类型**的引用。
:tag 是另一个重要的关键字参数,因为我们的新组件使用了 lazy 组件。
默认情况下,lazy 组件没有标签,
如果缺少标签,自定义缓冲区会显示整个组件的值(即正在自定义的用户选项的值)。
这通常不是合适的做法,因此我们为 binary-tree-of-string 组件提供了一个名称字符串。
:offset 参数用于确保子节点相对于父节点缩进 4 个空格,
让树结构在自定义缓冲区中清晰可见。
后面的 defcustom 展示了如何将新组件当作普通自定义类型使用。
lazy 这个名字的由来是:
其他复合组件在缓冲区中实例化时,会将其下层组件转换为内部形式。
这种转换是递归的,下层组件会继续转换 它们 的下层组件。
如果数据结构本身是递归的,这种转换就会造成**无限递归**。
lazy 组件可以避免这种递归:它只在需要时才转换其 :type 参数。