让树数据结构这样定义:
一棵树以一个节点为根。一个节点可以是叶子,也可以是一个内部节点,其一个或多个节点作为其子节点。
在某种伪OO编程语言中,我们可以这样定义一棵树:
Node := InnerNode | Leaf
Leaf {
isLeaf() : TRUE
}
InnerNode {
isLeaf() : FALSE
children() : List<Node>
}
Tree {
root() : Node
}
现在我们可以定义两个函数,'bad_code'和'good_code'。函数'bad_code'无法编译,其他函数可以编译:
function bad_code(Node anyNode) : void {
// this will give a compile time error "type Node does not define method children()"
anyNode.children();
}
function good_code(Node anyNode) : void {
// the compiler understands that all Nodes must have a method called isLeaf() which
// returns a boolean
let b : boolean <- anyNode.isLeaf();
if (b == FALSE) {
// this will not give a compile time error because the compiler can deduce that
// anyNode must be of type InnerNode which has the method children()
anyNode.children();
}
}
问题:
您正在描述的是编译器使用控制流图来缩小变量的类型,因此,当if
语句测试与变量类型相关的条件时,可以为if
语句的主体推断出相同的变量。]>
这称为控制流类型变窄
,例如Typescript。它纯粹是静态检查,在编译时完成,没有运行时的损失;实际上,Typescript中的类型在运行时根本不可用。type TreeNode = InnerNode | Leaf interface Leaf { isLeaf: true } interface InnerNode { isLeaf: false children: Node[] } function bad_code(anyNode: TreeNode): void { // type error: Property 'children' does not exist on type 'TreeNode'. console.log(anyNode.children); } function good_code(anyNode: TreeNode): void { if (!anyNode.isLeaf) { // narrowed type to anyNode: InnerNode console.log(anyNode.children); } }
请注意,打字稿要求您以特定方式执行此操作;我们直接测试
anyNode.isLeaf
而不是先将其存储在变量b: boolean
中,因为Typescript不会跟踪两个变量b
和anyNode
之间的关系:
function bad_in_typescript(anyNode: TreeNode): void { let b: boolean = anyNode.isLeaf; if (!b) { // type error: Property 'children' does not exist on type 'TreeNode'. console.log(anyNode.children); } }
此外,在以上代码中,
isLeaf
是属性而不是方法。 Typescript确实具有一个名为user-defined typeguards]的相关功能,该功能允许方法的返回类型类似于this is Leaf
,表示仅当调用类型为true
的事物时,该方法才返回Leaf
: >type TreeNode = InnerNode | Leaf interface BaseNode { ifLeaf(): this is Leaf isInner(): this is InnerNode } interface Leaf extends BaseNode {} interface InnerNode extends BaseNode { children(): Node[] }
但是,打字稿仍然比您的示例要受限制;我们必须测试
anyNode.isInner()
,因为!anyNode.isLeaf()
不一定会进行同样的缩小。 (Typescript使用结构类型,因此,实际上Leaf
是InnerNode
的超类型,这会导致并集类型出现一些问题。如果给Leaf
一个类似value: number
的属性,而InnerNode
则没有,然后!anyNode.isLeaf()
会按照您的期望进行工作。)