在这一节我们所设计的数据结构,灵感来自 HTML 格式,这是我们熟悉的,成功创建文档的语言。就像 HTML 一样,我们表示的内容有几种类型,并且有些部分可能以适当的方法嵌套。图 7.3 显示了带注释的示例文档,它能给你格式包括哪些内容的概念。
有两种不同的部分。简单的部分,如 TextPart 和 ImagePart,包含内容,但不包含嵌套的部分;在另一侧,TitledPart 包含了嵌套部分,还有标题,而 SplitPart 包含一个或多个嵌套的部分和方向的规范。你可能已经猜到,我们将使用差别联合来表示不同的部分;有两个部分可以包含嵌套的部分,因此,类型将是递归的。清单 7.9 显示了类型声明,给我们更具体的内容,需要详细讨论。
图 7.3 文档格式有四种不同部分:TitledPart 标题加其他部分;使用 SplitPart,可以创建列和行;TextPart 和 ImagePart 指定实际内容。
清单 7.9 分层次的文档表示 (F#)
type Orientation = [1]
|Vertical
|Horizontal
type DocumentPart = [2]
|SplitPart of Orientation * list<DocumentPart> <-- 行或列中包含其他部分
|TitledPart of TextContent * DocumentPart <-- 表示有标题的部分
|TextPart of TextContent | 保存基本内容
|ImagePart of string |
把前面一段中非正式的规范翻译成 F# 代码非常简单,这绝对是一个最标准的 F# 类型声明。我们首先声明一个简单的差别联合,有两个选项,表示拆分部分的方向[1],然后,再声明DocumentPart 类型[2],有四个可选的选项。
两个选项递归地包含了其他文档部分。SplitPart 包含了列表中的几个其他部分,和确定区域应如何划分的方向信息;TitledPart 包含了一个其他部分,和装饰用的标题。保存文本,使用上一节的 TextContent 类型,它是记录类型,包含了字符串和使用的字体。
DocumentPart 类型表示整个文档。因为,类型是递归的,我们可以在一个文档部分中,嵌套任意数量的内容部分。这不同于以前的做法,那时,我们为元素创建类型,然后,用元素列表表示文档;在那种表示形式中,列表成为数据结构的“根”,元素没有进一步地嵌套;使用新的数据类型,我们可以像这样来写 7.2 节的文档:
let doc =
TitledPart({Text = "Functional Programming for the Real World";
Font = fntHead },
SplitPart(Vertical,
[ImagePart("cover.jpg");
TextPart({Text = "..."; Font = fntText }) ]
)
)
我们省略了位于图像下面的TextPart 的内容,但仍可以看到,表示方法很简洁,因为不需要自己计算矩形的边框。然而,我们没有实现绘制数据类型。我们不打算写,或者说,什么时候,我们已经为早先的表示方法,有了很好的绘制函数,为什么还要写呢?我们所要做的就是提供转换,从“构造设计”的形式到“绘制设计”。