对象变体
在需要简单变体类型的某些情况下,对象层次结构通常有点过了。 对象变体是通过用于运行时类型灵活性的枚举类型区分的标记联合,对照如在其他语言中找到的 sum类型 和 代数数据类型(ADT) 的概念。
一个示例:
- # 这是一个如何在Nim中建模抽象语法树的示例
- type
- NodeKind = enum # 不同的节点类型
- nkInt, # 带有整数值的叶节点
- nkFloat, # 带有浮点值的叶节点
- nkString, # 带有字符串值的叶节点
- nkAdd, # 加法
- nkSub, # 减法
- nkIf # if语句
- Node = ref NodeObj
- NodeObj = object
- case kind: NodeKind # ``kind`` 字段是鉴别字段
- of nkInt: intVal: int
- of nkFloat: floatVal: float
- of nkString: strVal: string
- of nkAdd, nkSub:
- leftOp, rightOp: Node
- of nkIf:
- condition, thenPart, elsePart: Node
- # 创建一个新case对象:
- var n = Node(kind: nkIf, condition: nil)
- # 访问n.thenPart是有效的,因为 ``nkIf`` 分支是活动的
- n.thenPart = Node(kind: nkFloat, floatVal: 2.0)
- # 以下语句引发了一个 `FieldError` 异常,因为n.kind的值不合适且 ``nkString`` 分支未激活:
- n.strVal = ""
- # 无效:会更改活动对象分支:
- n.kind = nkInt
- var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4),
- rightOp: Node(kind: nkInt, intVal: 2))
- # valid:不更改活动对象分支:
- x.kind = nkSub
从示例中可以看出,对象层次结构的优点是不需要在不同对象类型之间进行转换。 但是,访问无效对象字段会引发异常。
对象声明中 case 的语法紧跟着 case 语句的语法: case 部分中的分支也可以缩进。
在示例中, kind 字段称为 鉴别字段 : 为安全起见,不能对其进行地址限制,并且对其赋值受到限制:新值不得导致活动对象分支发生变化。 此外,在对象构造期间指定特定分支的字段时,必须将相应的鉴别字段值指定为常量表达式。
而不是更改活动对象分支,将内存中的旧对象完全替换为新对象:
- var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4),
- rightOp: Node(kind: nkInt, intVal: 2))
- # 更改节点的内容:
- x[] = NodeObj(kind: nkString, strVal: "abc")
从版本0.20开始 system.reset 不能再用于支持对象分支的更改,因为这从来就不是完全内存安全的。
作为一项特殊规则,鉴别字段类型也可以使用 case 语句来限制。 如果 case 语句分支中的鉴别字段变量的可能值是所选对象分支的鉴别字段值的子集,则初始化被认为是有效的。 此分析仅适用于序数类型的不可变判别符,并忽略 elif 分支。
A small 示例:
- let unknownKind = nkSub
- # 无效:不安全的初始化,因为类型字段不是静态已知的:
- var y = Node(kind: unknownKind, strVal: "y")
- var z = Node()
- case unknownKind
- of nkAdd, nkSub:
- # valid:此分支的可能值是nkAdd / nkSub对象分支的子集:
- z = Node(kind: unknownKind, leftOp: Node(), rightOp: Node())
- else:
- echo "ignoring: ", unknownKind