不是C语言的union哦。
问题
假设下面不是ruby代码,duck的类型是什么?(代码来自《冒号课堂》)
rubydef shoutOrSwim(duck, flag)
flag ? duck.shout : duck.swim
end
你要先想想,然后我的答案在这里。
Union types (in Ceylon)
除了依赖类型,还有没有其他类型系统可以表达“duck
支持swim
或swim
”?union types能直接表达这种或关系,下面是Ceylon的实现:
interface ICanShout {
shared formal void shout();
}
interface ICanSwim {
shared formal void swim();
}
void shoutOrSwim(ICanSwim | ICanShout duck, Boolean flag) {
if (flag) {
if (is ICanSwim duck) {
duck.swim();
}
} else {
if (is ICanShout duck) {
duck.shout();
}
}
}
(ICanXXX
是很逗逼的命名方法)
这个实现的约束没依赖类型的强,但是更好理解,也容易使用;和Ruby相比,因为要先经过编译期检查,所以更安全。
ICanSwim | ICanShout
是一个类型,ICanSwim
和ICanShout
都是它的子类。当我们知道duck
的类型是ICanSwim | ICanShout
时,我们怎么样使用duck
呢?因为我们还不知道duck
的具体类型,我们只能用ICanSwim
和ICanShout
共有的方法(在Ceylon里就是公共父类里的方法),这时你能做的其实是功能的交集,所以很容易和intersection type混淆,union指的是值集的并。
我们也可以返回union type:
String | Integer baka(Boolean flag) {
if (flag) {
return "Cirno";
} else {
return 9;
}
}
Hack就是用类似的方法对PHP代码进行类型检查。
Narrowing
但是,我们拿到一个union type之后,我们只能使用交集里的方法,相当于失去部分的类型信息,而这些类型信息往往是有用的(我们不是实现抽象数据类型、不是要隐藏实现)。在Ceylon里我们可以用is
语句来检查类型,看一下duck
到底是哪个类型,然后在条件语句的分支里使用具体类型的duck。如果用Java,我们会先类型检查之后再转型:
Obejct duck = ……
if (duck instanceof ICanSwim) {
ICanSwim s = (ICanSwim)duck;
……
} else if (duck instanceof ICanShout) {
ICanShout s = (ICanShout)duck;
……
}
在Ceylon里duck
有更准确的类型(不是简单的Object
),而转型是自动完成,不会出现检查和转型不匹配的情况。
运行时类型检查
一个“足够聪明的编译器”应该能去掉一些不必要的运行时类型检查,例如可以出特化下面的代码:
void shoutOrSwim_for_ICanSwim(ICanSwim duck, Boolean flag) {
if (flag) {
duck.swim();
} else {}
}
void shoutOrSwim_for_ICanShout(ICanShout duck, Boolean flag) {
if (flag) {
} else {
duck.shout();
}
}
只要能在编译器知道参数的具体类型,就可以替换为特化后的代码。不过在有多个union type的参数,就可能组合爆炸。就算运行时类型检查是不可避免的,这都是安全的。这和对tagged union进行模式匹配很相似(tagged union也能被看作是运行时类型检查),但tagged union不可扩展,类型标签必须显式添加。