我刚刚发现存在类型(类似于Java的通配符类型,Ceylon 不支持也不会支持)的一个很好的用例。这有点复杂,围绕Ceylon的一个其他语言没有的高级特性:类型安全的元模型。但让我看看我是否能把这个想法解释清楚。
元模型引用作为间接引用
在Ceylon中,像这样的元模型引用Entry<String,File>有一个定义良好的类型,它捕获了声明定义的操作的签名——在这种情况下,Entry类的实例化。我们可以写Entry类。我们可以写
ConcreteClass<Entry<String,File>,String,File> stringFileEntryClass = Entry<String,File>;
碰巧的是,元模型类型ConcreteClass<T,P...>被定义为扩展函数类型Callable<T,P...>,所以我们可以写这样的东西
Entry<String,File> stringFileEntryClass(String s, File f) = Entry<String,File>; ... Entry<String,File> entry = stringFileEntryClass("out", File(outputFilePath));
这只是以下内容的间接写法
Entry<String,File> entry = Entry("out", File(outputFilePath));
这里发生的事情是stringFileEntryClass()是第一个类函数引用的别名Entry<String,File>它仅实例化一个Entry<String,File>.
不错,是吧?当我们说Ceylon是一种高阶语言时,我们就在谈论这个。我们可以使用任何命名的引用作为一等值,并且具有正确定义的类型。
类型构造函数的引用
但是,如果我们想在不知道其类型参数的具体参数的情况下使用Entry作为一等值,那怎么办呢。嗯,Entry本身不是一个类型,它是一个类型构造函数——一个接受两种类型作为参数并产生一个类型的函数——或者用我更倾向于避免的更加自负的术语,一个高阶类型。
所以,问题是,如果我们有一个类型构造器的元模型对象,我们能否在Ceylon的类型系统中正确地表示其类型?很遗憾,不能,因为Ceylon没有存在类型。在一个假设的语言“存在类型Ceylon”中,类型的指定是这样的:Entry以下是我们可以用它做的事情
ConcreteClassConstructor<Entry<U,V>,U,V> entryClassConstructor given U satisfies Equality given V satisfies Equality = Entry;
也就是说,我们在这里得到了额外的间接层次,使我们能够将类型参数作为普通的一级值传递。(实际上,能够在Ceylon中想象出这种工作方式是非常酷的。)
Entry<U,V> entryClassConstructor<U,V>(Type<U> ut, Type<V> ut)(U u, V v) given U satisfies Equality given V satisfies Equality = Entry; ... Entry<String,File> stringFileEntryClass(String s, File f) = entryClassConstructor(String,File); ... Entry<String,File> entry = stringFileEntryClass("out", File(outputFilePath));
不幸的是,在Ceylon中,我们无法编写这样的东西,因为我们不能在没有存在类型支持的情况下正确地定义值的类型
就像以前一样,语言设计都是关于妥协的。Entry运算符引用
还有另一种情况,同样的限制出现了。Ceylon的运算符是用方法定义的,在许多情况下是参数化方法,或者参数化类型的方法。例如,
运算符是用以下方法定义的<smallerThan()的Comparable<T>。今天早上我自己在想,Ceylon是否应该支持像(从OCaml借用的语法)这样的语法来引用运算符。但我意识到这存在上面描述的问题。由于Comparable的类型参数,(<)的类型无法表达,不使用存在类型或某种左至右的类型推断是不可能的。所以你永远无法在Ceylon中编写(<)。你能做到的最好的是像这样UPDATE:实际上,我想到一个合理的解决方案。我们正在考虑支持支持单引号字面量的左至右类型推断的特殊情况,这将被限制在只有左至右类型推断可能发生的地方。所以,以下语法可能会工作,我想这是一个更深层次的问题吗?
names.sortedBy((<))
这让我开始思考。《类型构造器参数化》(通常称为“支持高阶类型”)是我们想知道Ceylon最终是否应该支持的事情。我谈的是以下这样的方法签名,其中参数
names.sortedBy(smaller<String>)
M
names.sortedBy('<')
不是一个接受类型的类型参数,而是一个
类型构造器参数,接受类型构造器A
M<U> map<M<T>,U,V>(U function(V v), M<V> inputs) given M<T>() satisfies Iterable<T> & Addable<T> { variable M<U> outputs := M<U>(); for (V input in inputs) { outputs:=ouputs.add(function(input)); } return outputs; }
从上面的讨论中自然产生的疑问是,在不存在存在类型的情况下,是否甚至可以支持类型构造器参数化。
嗯,我认为可以。原因是类型参数——以及通过扩展类型构造器参数——不是作为普通值传递的,而是在特殊的类型参数列表中静态指定的,这个列表由尖括号分隔。Ceylon,与其他C-like语言一样,不允许你编写以下内容
Interface<Sequence<String>> stringSequenceType = Sequence<String>; Sequence<String> hello = singleton<stringSequenceType>("Hello"); //error: stringSequenceType isn't a legal type argument
因此,编译器不需要能够将Ceylon类型分配给类型构造器参数来进行必要的类型检查。在某种程度上,编译器需要能够确定类型构造器到类型构造器参数的“可分配性”,但它能够超出通常一级值的类型系统范围来进行这种判断。(这当然与Java使用类型参数的方式没有区别,因为在Java中类型不是一级值。)
一些猜测
有趣的是,Ceylon可能已经走了一条不同的路。该语言非常独特地具有
- 具现泛型
- 和类型安全的元模型。
因此,并没有真正强有力的理由说明为什么Ceylon需要特殊的中括号语法来表示类型参数。在Ceylon中,类型是第一等值。从理论上讲,我们可以将类型参数视为任何其他类型的值。甚至可能会实际上简化类型检查器。
至少现在我们已经找到了不采取这条道路的一个原因。这意味着要么
- 永远放弃支持类型构造函数参数化的想法,或者
- 引入存在类型。
哦,顺便说一句...
...如果你不理解这篇文章,请不要担心。实际上,编写有用程序的人并不需要或想了解存在类型和类型构造函数参数化是什么。这是一个在设计语言时需要牢记的重要事实。如果一种语言的概念框架足够接近人们在用该语言编写程序时想要思考的事物,那就更好了。