Union types (Part 1)

March 28, 2014

不是C语言的union哦。

问题

假设下面不是ruby代码,duck的类型是什么?(代码来自《冒号课堂》)

rubydef shoutOrSwim(duck, flag)
    flag ? duck.shout : duck.swim
end

你要先想想,然后我的答案在这里

Union types (in Ceylon)

除了依赖类型,还有没有其他类型系统可以表达“duck支持swimswim”?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是一个类型,ICanSwimICanShout都是它的子类。当我们知道duck的类型是ICanSwim | ICanShout时,我们怎么样使用duck呢?因为我们还不知道duck的具体类型,我们只能用ICanSwimICanShout共有的方法(在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不可扩展,类型标签必须显式添加。