转换构造函数类型转换函数
我之前关于类型函数的文章在此处以及关于reddit引起了一些有趣的讨论。
因此,我认为有必要将早期文章中的几个松散结局捆绑在一起。 因此,这是有关类型函数的更多观察结果的集合。
警告:本文讨论了一些非常技术上的细节,说明了我们如何将类型函数合并到锡兰的类型系统中。 甚至不要打扰继续任何进一步的,直到你读ē arlier岗位 。
这个“为什么”
高阶泛型的最著名应用是表示容器类型的高级抽象:函子,单子和朋友。 这并不是促使我在锡兰使用高阶泛型进行实验的动机,并且实际上,尽管这些抽象很有趣,但我对这些抽象仍然不感兴趣。
不,令我困扰的不是锡兰的Functor
系统不够强大,无法代表Functor
或Monad
,而是锡兰的Monad
体系不够强大,无法代表锡兰 。 我将在一秒钟内告诉您我的意思。 但是首先,我想指出类型函数可以看作是语言的正则化。
从纯粹的语法角度来看,Ceylon中的每种类型声明都可以具有类型参数列表,这似乎总是有些奇怪, 除了类型参数本身 。 此外,值得注意的是, 除非有类型参数列表,否则我可以引用任何程序元素的引用或元引用。 现在,如果参数化类型参数或对泛型声明的引用在根本上不是有意义的概念,则这种限制似乎是合理的。 但是它们显然是有意义的,甚至至少是有用的。
另一个问题究竟有多有用?至少在我看来,陪审团还没有成立。 因此,也许我们最终可以得出结论,这些东西不值钱。 实际上,权重是指刚接触Ceylon的程序员理解这些东西所花费的时间。
在原始帖子中,我展示了如何需要类型函数来表示对泛型函数的引用的类型。 出现此问题的地方之一是锡兰最独特的功能之一:其类型安全的元模型 。
通用函数引用类型的用例
通常,我可以获得代表类或函数并捕获其类型签名的元模型对象。 例如,表达式`String`
为一个元模型对象,该对象捕获String
类的类型和初始化参数:
Class<String,[{Character*}]> stringClass = `String`;
对于泛型声明,只要我准备确定类型参数,我就可以做类似的事情。 例如,我可以在应用类型参数String
之后编写`Singleton<String>`
以获取表示类Singleton
的元模型对象:
Class<Singleton<String>,[String]> stringSingletonClass
= `Singleton<String>`
但是在今天存在的锡兰中,我无法获得仅表示`Singleton`
的类型化元模型对象,因为要表示该元模型对象的类型,我必然需要类型函数。
现在,有了对类型函数的新实验支持,表达式`Singleton`
的类型可以是<T> => Class<Singleton<T>,[T]>()
,从而允许如下代码:
value singletonGenericClass = `Singleton`;
...
Class<Singleton<String>,[String]> stringSingletonClass
= singletonGenericClass<String>();
那只是允许对通用函数的引用使Ceylon感觉“更完整”的一个示例。
匿名类型函数的两个用例
我得到的印象是,我在上一篇文章中介绍的“最可怕”的地方是匿名类型函数的表示法。 即,以下语法:
<X> => X
<X> => [X,X,X]
<X,Y> => X|Y
<T> given T satisfies Object => Category<T>(T*)
但是我坚信,这种表示并不是真的很难理解。 我之所以断言,是因为如果给每个这些类型的函数起一个名字,那么大多数人对它们的理解都没有问题:
alias Identity<X> => X;
alias Triple<X> => [X,X,X];
alias Union<X,Y> => X|Y;
alias CategoryCreator<T> given T satisfies Object => Category<T>(T*);
但是 ,如果命名版本更易于阅读,那么我们可能甚至会问他们为什么还要使用它们 ?
好吧,我们需要它们:
- 为了能够表示对通用函数的引用的类型(请记住,在锡兰,我们没有不可表示的类型),并且
- 使其易于部分应用诸如
Map
的命名类型函数。
例如,我们希望能够在使用高阶泛型时编写诸如<T> => Map<String,T>
类的东西,从而将两个类型参数的类型函数转换为一个类型参数的类型函数。
类型函数是“类型类型”吗?
我应该非常清楚并且忘记的一件事是,类型函数不代表附加的元级别。 在锡兰的类型系统中,类型函数是类型,从某种意义上说,功能是锡兰和其他现代语言中的值。
锡兰中根本没有针对类型的附加元类型系统。 我们最接近“类型类型”的是泛型类型约束,但这是一种极其贫乏的类型类型,因为Ceylon根本不提供任何设施来抽象出类型约束—我什至无法为类型约束并按名称重用。
锡兰语关于类型约束和类型可分配性的原因是使用最初写入语言规范和类型检查器的硬编码规则,而不是通过对类型类型的抽象。
类型函数和子类型
但是,如果类型函数是类型,那么它与其他类型和其他类型函数的子类型关系是什么?
好吧,首先,请回想一下某些类型函数具有实例:泛型函数引用。 我们不想在不是Object
的语言中引入值,因此我们已经声明每个类型函数都是Object
的子类型。 这保留了有用的属性,即我们的类型系统具有单个根类型Anything
。
接下来,回忆一下,一个普通的函数类型是它的返回类型协变 ,并在其参数类型的逆变 。 例如,函数类型:
String(Object, Object)
是以下内容的子类型:
Object(String, String)
因为如果一个函数接受两个Object
并返回一个String
,那么显然它也是一个接受两个String
并返回Object
的函数。
给定两种具有一个参数的函数类型:
F(P)
G(Q)
那么F(P)
是G(Q)
的子类型,如果P
是Q
的超类型,而F
是G
的子类型。
类似的规则适用于类型函数。 考虑一个类型参数的两个类型函数:
alias A<X> given X satisfies U => F<X>
alias B<Y> given Y satisfies V => G<Y>
那么A
是B
的子类型:
-
X
的上限U
是Y
的上限V
的超型,并且 - 对于任何类型
T
,F<T>
是G<T>
的子类型。
也就是说,如果A<X>
接受B<Y>
A<X>
接受的每个类型参数T
,并且对于每个这样的T
,则应用的类型A<T>
是应用的类型B<T>
的子类型,则我们可以用正确键入的代码将B
替换为A
(当然,这些规则一般适用于具有多个类型参数的类型函数。)
通用函数类型和子类型
现在让我们集中精力只考虑代表泛型函数类型的类型函数。 为了简化操作,我们将考虑以下形式的泛型函数,其中只有一个类型参数和一个值参数:
F<X> f<X>(P<X> p) given X satisfies U => ... ;
在这里, F<X>
是返回类型,它是涉及类型参数X
的类型表达式,而P<X>
是参数类型,它也涉及X
正如我们在上一篇文章中看到的那样,该泛型函数的类型是类型函数:
<X> given X satisfies U => F<X>(P<X>)
因此,让我们考虑一下我们正在考虑的一般形式的两个类型函数:
alias A<X> given X satisfies U => F<X>(P<X>)
alias B<Y> given Y satisfies V => G<Y>(Q<Y>)
然后我们很快就会看到A
是B
的子类型:
-
X
的上限U
是Y
的上限V
的超型,并且 - 对于任何类型的
T
,返回类型F<T>
的A
是返回类型的子类型G<T>
的B
,和参数类型P<T>
的A
是参数类型的超类型Q<T>
的B
例如,以下通用函数类型:
<X> => X&Object(X|Object)
是此泛型函数类型的子类型:
<X> given X satisfies Object => X(X)
花一点时间说服自己,这在直觉上是正确的。
(同样,这些规则自然地推广到具有多个类型参数和/或多个值参数的函数。)
类型函数和类型推断
当我们在Ceylon中调用一阶泛型函数时,通常不需要显式指定类型参数。 相反,我们通常可以从调用表达式的value参数推断出它们。 例如,如果我们具有以下通用功能:
List<Out> map<In,Out>(Out(In) fun, List<In> list) => ... ;
然后,我们总是可以安全地推断In
和Out
,因为类型参数有一个唯一的最精确的选择:
value list = map(Integer.string, ArrayList { 10, 20, 30 });
在此示例中,我们可以安全地推断In
为Integer
, Out
为String
,而不会损失任何精度。
不幸的是,一旦高阶泛型发挥作用,类型函数的推断就变得更加模棱两可了。 考虑一下此二阶泛型函数,它通过引入类型函数变量Box
来表示未知的容器类型,从而使map()
函数从容器类型中抽象出来:
Box<Out> fmap<Box,In,Out>(Out(In) fun, Box<In> box)
given Box<Element> { ... }
现在考虑对该函数的以下调用:
fmap(Integer.string, ArrayList { 10, 20, 30 })
我们应该为Element
推断什么类型,为Box
推断什么类型的功能?
-
Integer
和List
? -
Integer
和可Iterable
? -
Integer
和ArrayList
? -
Integer
和MutableList
? -
Integer
和ListMutator
? -
Integer
和Collection
? -
Object
和Category
?
通常,可能会有几种不同的合理选择,而没有真正好的选择标准。 因此,在这种情况下,我们需要明确指定类型参数:
fmap<List,Integer,String>(Integer.string, ArrayList { 10, 20, 30 })
但是,有一种模式可以用来使类型函数推断成为可能。 在这种情况下,我们可以定义以下接口:
interface Functor<Box,Element> given Box<Value> { ... }
现在让我们想象一下,我们的ArrayList
类继承了Functor
,因此任何ArrayList<Element>
都是Functor<List,Element>
。
让我们像这样重新定义fmap()
:
Box<Out> fmap<Box,In,Out>(Out(In) fun, Functor<Box,In> box)
given Box<Element> { ... }
然后,最后,对于我们之前具有的相同实例化表达式:
fmap(Integer.string, ArrayList { 10, 20, 30 })
我们现在可以清楚地推断出Box
是List
和In
是Integer
,因为这些类型被编码为类型参数的Functor
中的主超类型实例化Functor
用于表达ArrayList { 10, 20, 30 }
类型函数的实例
在原始帖子中,我们注意到返回函数类型的类型函数是泛型函数的类型。 例如,类型函数:
<X> given X satisfies Object => X(X)
是此泛型函数的类型:
X f<X>(X x) given X satisfies Object => x;
但是很自然地要问:如果某些类型函数是泛型函数的类型,那么其他类型函数又是什么类型?
好吧,如果您仔细考虑一下类型和值之间的关系,我想您会发现它们必须是通用值声明的类型。 也就是说,这种类型的功能:
<X> => List<X>
将是此值的类型,用伪锡兰编写:
List<X> list<X> => ... ;
也就是说,当呈现类型X
, list<X>
计算结果为List<X>
。
当然,锡兰中没有实际的泛型值,我们最接近的是一个空泛型函数:
List<X> list<X>() => ... ;
其类型实际上是:
<X> => List<X>()
没有计划将通用值引入Ceylon,因此<X> => List<X>
这样的类型没有实例。 它们仅用作高阶泛型类型的类型参数。
类型函数和主体类型
最后,让我们解决一个相当技术性的问题。
锡兰类型系统的一个非常重要的属性是能够形成泛型类型的不同实例的任何并集或交集的主体实例的能力。
例如,对于协变类型List<T>
:
-
List<X> | List<Y>
List<X> | List<Y>
具有主实例化List<X|Y>
-
List<X> & List<Y>
具有主要实例List<X&Y>
对于对照类型Comparable<T>
:
-
Comparable<X> | Comparable<Y>
Comparable<X> | Comparable<Y>
具有主要实例Comparable<X&Y>
-
Comparable<X> & Comparable<Y>
具有主要实例Comparable<X|Y>
自然地,重要的是我们可以对高阶类型的实例的交集和并集执行相同的技巧。 碰巧的是,使用以下身份非常自然地解决了这一问题:
-
<<X> => F<X>> | <<Y> => G<Y>>
<<X> => F<X>> | <<Y> => G<Y>>
是<T> => F<T> | G<T>
的子类型。<T> => F<T> | G<T>
和 -
<<X> => F<X>> & <<Y> => G<Y>>
是<T> => F<T> & G<T>
。
因此,如果我们具有以下协变二阶类型:
interface Functor<out Element, out Container>
given Container<E> { ... }
然后,我们获得以下主要实例:
-
Functor<E,A> | Functor<F,B>
Functor<E,A> | Functor<F,B>
具有主要实例化Functor<E|F,<T> => A<T>|B<T>>
,并且 -
Functor<E,A> & Functor<F,B>
具有主要实例化Functor<E&F, <T> => A<T>&B<T>>
。
在用Ceylon编写代码时,您不需要了解这些身份,但是很高兴知道类型函数不会破坏基本的代数属性,这就是Ceylon的类型系统非常适合使用的原因。 一切都在这里放在一起,没有怪异的Kong和角盒。
关于“等级”的字眼
在上一篇文章中,我描述了我们对将泛型函数引用为“任意等级”多态性的支持,这促使人们简短讨论如何测量泛型类型的等级。 我现在认为,“等级”一词对Ceylon来说可能不是很有意义,因为我们的函数类型没有什么特别的:它们只是完全普通的通用类型Callable
实例化。 正如Kamatsu 在reddit上建议的那样, 在我看来,使用一个更好的词可能是“强制性的”。
翻译自: https://www.javacodegeeks.com/2015/06/a-little-more-about-type-functions.html
转换构造函数类型转换函数