对象变体

在需要简单变体类型的某些情况下,对象层次结构通常有点臃肿。对象变体是通过枚举类型标记和区分,以便运行时更加灵活,可参照在其他语言中能找到的如 sum类型代数数据类型(ADTs) 的概念。

一个例子:

  1. # 这是一个如何在 Nim 中建模抽象语法树的示例
  2. type
  3. NodeKind = enum # 不同的节点类型
  4. nkInt, # 带有整数值的叶节点
  5. nkFloat, # 带有浮点值的叶节点
  6. nkString, # 带有字符串值的叶节点
  7. nkAdd, # 加法
  8. nkSub, # 减法
  9. nkIf # if 语句
  10. Node = ref NodeObj
  11. NodeObj = object
  12. case kind: NodeKind # `kind` 字段是鉴别字段
  13. of nkInt: intVal: int
  14. of nkFloat: floatVal: float
  15. of nkString: strVal: string
  16. of nkAdd, nkSub:
  17. leftOp, rightOp: Node
  18. of nkIf:
  19. condition, thenPart, elsePart: Node
  20. # 创建一个新 case 对象:
  21. var n = Node(kind: nkIf, condition: nil)
  22. # 访问 `n.thenPart` 是有效的,因为 `nkIf` 分支是活动的
  23. n.thenPart = Node(kind: nkFloat, floatVal: 2.0)
  24. # 以下语句引发了一个 `FieldError` 异常,因为 n.kind 的值不合适且 `nkString` 分支未激活:
  25. n.strVal = ""
  26. # 无效:会更改活动对象分支:
  27. n.kind = nkInt
  28. var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4),
  29. rightOp: Node(kind: nkInt, intVal: 2))
  30. # 有效:不更改活动对象分支:
  31. x.kind = nkSub

从示例中可以看出,对象层次结构的优点是,不需要在不同对象类型之间进行转换。但是,访问无效对象字段会引发异常。

在对象声明中的 case 语句和标准 case 语句语法一致: case 语句的分支也是如此。

在示例中, kind 字段称为 discriminator “鉴别字段” ,为安全起见,不能对其进行地址限制,并且对其赋值进行限制: 新值不得导致活动对象分支发生变化。 此外,在对象构造期间指定特定分支的字段时,必须将相应的鉴别字段值指定为常量表达式。

与改变活动的对象分支不同,可以将内存中的旧对象换成一个全新的对象。

  1. var x = Node(kind: nkAdd, leftOp: Node(kind: nkInt, intVal: 4),
  2. rightOp: Node(kind: nkInt, intVal: 2))
  3. # 改变节点的内容
  4. x[] = NodeObj(kind: nkString, strVal: "abc")

从版本 0.20 开始 system.reset 不能再用于支持对象分支的更改,因为这始终不是完全内存安全的。

作为一项特殊规则,鉴别字段类型也可以使用 case 语句来限制。如果 case 语句分支中的鉴别字段变量的可能值是所选对象分支的鉴别字段值的子集,则认为是有效的初始化。 此分析仅适用于序数类型的不可变判别符,并忽略 elif 分支。对于具有 range 类型的鉴别值,编译器会检查鉴别值的整个可能值范围是否对所选对象分支有效。

一个小例子:

  1. let unknownKind = nkSub
  2. # 无效:不安全的初始化,因为类型字段不是静态已知的:
  3. var y = Node(kind: unknownKind, strVal: "y")
  4. var z = Node()
  5. case unknownKind
  6. of nkAdd, nkSub:
  7. # 有效:此分支的可能值是 nkAdd / nkSub 对象分支的子集:
  8. z = Node(kind: unknownKind, leftOp: Node(), rightOp: Node())
  9. else:
  10. echo "ignoring: ", unknownKind
  11. # 同样有效, 因为 unknownKindBounded 只包含 nkAdd 或 nkSub
  12. let unknownKindBounded = range[nkAdd..nkSub](unknownKind)
  13. z = Node(kind: unknownKindBounded, leftOp: Node(), rightOp: Node())