Java数组不是完全类型安全的,因为它们是协变的:ArrayStoreException可以发生在别名数组上。另一方面,Java在其类型参数中是不变的:例如,List<Thread>不是List<Runnable>的子类型(这可能有点违背直觉)。动机似乎是因为List和其他集合是可变的,因此为了保持类型系统的正常运行,它们的类型参数必须是不变的。
如果编程语言只支持不可变类型,那么类型参数为协变或反变(但从不不变)的类型系统能工作吗?换句话说,要使用Scala表示方差的方法,您可以使用List[+E]、Function[-T, +R]、Map[+K, +V]等。我知道有些较老的语言(例如GNU Sather)似乎只支持协/反变参数类型。
我的一般问题是:在一个完全不可变的数据类型的世界中,是否存在一个特定需要不变参数类型(而不是协变量或禁忌变量)的情况?对于只有在不变类型参数下才正确的不可变数据结构,是否有一些示例?
发布于 2020-04-03 17:56:29
所以,每种类型的系统要么允许一些不健全的程序,要么禁止一些健全的程序,或者两者都允许(这是赖斯定理的结果),所以一个很好的工作假设是,是的,你想出的任何限制都必然排除一些本来允许的健全程序。另一方面,人类是无限聪明的,所以从另一个意义上说,答案是否定的:如果你像你描述的那样加上一个狭窄,没关系,人们会在需要的时候想出一个绕过它的方法。(当然,有时候他们想出的解决办法是你不喜欢的,比如放弃你的语言。)
但我认为你真正想要的是一个有说服力的例子:一个现实的例子,在直接支持这个例子和坚持要求所有类型的参数都是协变或反变之间的选择,你的直觉会告诉你放弃这个提议,这样你就可以直接支持这个例子了。
由于您已经确定了类型参数不能是协变量的各种情况,以及类型参数不能反变的各种情况(例如,函数-T,+R很好,但相反则完全不健全),所以一种很好的方法是搜索两次使用相同类型参数的情况,一次是不能协变的情况,一次是不能反变的方式。一个简单的例子是UnaryOperatorT <:FunctionT,T,类似于Java的java.util.function.UnaryOperator,它的'apply‘方法返回与它接受的相同类型。UnaryOperatorString不能用作UnaryOperator对象,但UnaryOperatorObject也不能用作UnaryOperatorString (因为即使您传递一个字符串,它也可能返回一些不同的对象)。
一个更真实的例子。。。假设一个二进制搜索树TreeMapK,+V <:MapK,V,类似于java.util.TreeMap。想必我们希望支持诸如'firstKey‘、'floorEntry’和‘迭代器’等方法(或者至少是其中的一些),所以我们不能使K反变: TreeMapObject,Foo不能用作TreeMapString,Foo,因为当我们检索一个键时,键可能不是字符串。
因为它是二进制搜索树,它需要内部的ComparatorK,这就使得K很难成为协变量:如果您使用TreeMapString、Foo作为TreeMapObject、Foo,那么您就隐式地使用ComparatorString作为ComparatorObject,这是不起作用的。现在,由于映射当然只包含字符串键,所以'get‘方法可能可以通过在使用ComparatorString调用之前预先检查键的类型来解决这个问题;但是'floorEntry’和'ceilingEntry‘方法仍然是一个问题:在不能与映射中的键进行比较的任意对象之前或之后出现了什么条目?
尽管您已经说过您的映射是不可变的,但您可能仍然需要某种“put”方法,这是一个纯功能的方法,它返回映射的修改副本。(纯功能的红黑树与可变的不变量和最坏情况的渐近时间复杂度相同,因此,抛开系统类型不谈,这肯定是一件合理的事情。)但是,如果TreeMapString,Foo可以用作TreeMapObject,Foo,那么它的“put”方法需要支持返回包含非字符串键的二进制搜索树--尽管它的ComparatorString没有为这些键定义排序。
(在注释中,您提到Scala实际上用不变的键类型定义了MapK,+V。我从未使用过Scala,但我敢打赌这正是原因所在。)
https://stackoverflow.com/questions/60860479
复制相似问题