[转帖]Scala的类型系统:取代复杂的通配符_Android, Python及开发编程讨论区_Weblogic技术|Tuxedo技术|中间件技术|Oracle论坛|JAVA论坛|Linux/Unix技术|hadoop论坛_联动北方技术论坛  
网站首页 | 关于我们 | 服务中心 | 经验交流 | 公司荣誉 | 成功案例 | 合作伙伴 | 联系我们 |
联动北方-国内领先的云技术服务提供商
»  游客             当前位置:  论坛首页 »  自由讨论区 »  Android, Python及开发编程讨论区 »
总帖数
1
每页帖数
101/1页1
返回列表
0
发起投票  发起投票 发新帖子
查看: 3009 | 回复: 0   主题: [转帖]Scala的类型系统:取代复杂的通配符        下一篇 
shuangqiang.xing
注册用户
等级:上尉
经验:737
发帖:64
精华:0
注册:2013-10-30
状态:离线
发送短消息息给shuangqiang.xing 加好友    发送短消息息给shuangqiang.xing 发消息
发表于: IP:您无权察看 2013-11-5 16:33:11 | [全部帖] [楼主帖] 楼主

        Scala的类型映射Java通配符的有Existential类型,类型的可变性,以及抽象类的功能。

Existential类型

Bill Venners:最近Scala中添加了一些Existential(存在)类型。我听说添加Existential类型的理由是为了可以映射所有Java类型到Scala类型,特别是Java的通配符类型。Existential类型数量是否多于Java通配符类型?它们是否是Java通配符类型的一个扩展集?是否存在其他人们应该了解它的理由?

Martin Odersky:这很难说,因为人们并没有一个真正的关于什么是通配符概念。最初由Atsushi Igarashi和Mirko Viroli设计的通配符,其灵感来源于Existential类型。事实上,最初的论文中存在一个使用Existential类型的编码。但后来当实际最终设计在Java中实现时,这种联系就减少了一些。所以,现在我们真的不了解这些通配符类型的状况。

Existential类型已经出现许多年了,至今为止大约有20年左右。这种类型可以很简单地表达信息。例如你有一个类型,也许是list(列表)类型,其中一个列表项的类型你不知道,你只知道它是一些特殊元素类型的列表,但你不知道元素的类型。在Scala中,这就可以表示成一个Existential类型。语法是List[T] forSome { type T }.。这看起来有点繁琐。这种繁琐的语法实际上是故意的,因为它产生的Existential类型通常有点难以处理。现在,Scala有了更好的选择。它并不需要这么多Existential类型,因为我们可以使用包含其他类型成员的类型。 

首先,我们需要弄清一些Java通配符的意思,Existential类型就是我们所理解的意思。其次,我们需要弄清一些Java raw(原始)类型的意思,因为它们仍处于类库中,是ungenerified(非属性的)类型。如果你使用一个Java原始类型,如java.util.List,这是一个列表,你不知道列表元素的类型。在Scala中这可以被表示成一个Existential类型。最后,我们需要使用Existential类型来解释在虚拟机上发生着什么事情。Scala像Java一样,使用泛型擦除模式,所以当程序运行时,我们不再能看到类型参数。为了能与Java互用,我们需要进行擦除操作。但是,当我们做映射或想要表示时,在虚拟机上会发生什么事情?我们需要能够表达虚拟机在使用Scala中的类型时做了什么事情,Existential类型让我们做到了这一点。Existential类型可以让你在不了解类型中某些方面的情况下使用它们。

Bill Venners:您能举一个具体的例子吗?

Martin Odersky:以Scala lists(列表)为例。我希望能够描述方法的返回类型,head,它会返回列表第一个元素(头一个)。在VM水平,这是一个List[T] forSome { type T }。我们不知道T是什么。Existential类型理论告诉我们,这是一个适合某个类型T的T。这相当于根类型——对象。因此,我们从head方法得到这个类型。因此在Scala中,当我们知道某个类型时,我们可以消除这些Existential限制。当我们不知道某个类型时,我们就可以使用Existential,Existential类型理论就是在这里给予我们帮助。 

Bill Venners:如果您没有必要担心与Java通配符、原始类型和擦除的兼容性,还会添加Existential类型吗?如果Java拥有具体化的类型,没有原始类型和通配符,那么Scala还会有Existential类型吗?

Martin Odersky:如果Java拥有具体化的类型,没有原始类型和通配符,我认为Existential类型的使用量就没那么大了,那么我会考虑Scala不使用它。

可变性 

Bill Venners:在Scala中,是在定义类的时候定义可变性(variance),而在Java中,是在使用通配符的地方定义它。您能否谈谈这一差异?

Martin Odersky:由于我们可以在Scala中使用Existential类型建模通配符,实际上如果你想,你也可以在Java中做同样的事情。但是,我们不鼓励你这么做,而是建议使用定义地点可变性(definition site variance)来代替。这是为什么?首先,什么是定义地点可变性?当你定义一个带有一个类型参数的类时,例如List[T],这就带来了一个问题。如果你有一个苹果列表,那么它同样也是一个水果列表吗?你会说,当然是的。如果苹果是水果的一个子类型,那么List[Apple]应该是List[Fruit]的一个子类型。这种子类型关系被称为协变(covariance)。但在某些情况下,这种关系并不有效。如果我有一个变量,变量可以保存一个苹果,可以保存苹果类型的一个引用。但,这不是水果类型的一个引用,因为我不能分配任何其它水果给这个变量,它只能是一个苹果。所以,你可以看到,有些情况下我们应该有子类型,而有些情况下,子类型就不应该有。

Scala的解决方案是注释类型参数。如果List在T上是协变的,我们就可以写成List[+T]。这将意味着Lists在T上是协变的。当然这存在一些附属条件。例如,只有当没有人改变List的情况下,我们才可以这么做,否则我们将遇到使用引用时遇到的同样问题。  

在Scala中会发生什么事,这是程序员说的,我认为Lists应该是协变的,这意味着尊重子类型关系。然后,程序员将会在声明的地方,用一个加号修饰类型参数T,针对所有使用的List只修饰一次。然后编译器将去找出是否List内的所有定义都与其一致。如果存在某些与协变不符的地方,Scala编译器将会提示错误。Scala拥有一系列的技术来处理这些错误,一个有能力的Scala程序员将会很快注意到这些错误,并应用这些技术,最终生成一个错误处理类。使用者就不必再去顾虑这些错误了。他们只需知道如果我有一个List,我就可以在任何地方协变地使用它。因此,这意味着只有一个人在写list类,只有他需要考虑有点难度的问题,但这也不至于太糟糕,因为编译器会用错误提示帮助他。 

相比之下,Java带有通配符的方法意味着在类中你什么都做不了。你只是写List﹤T>。然后,如果用户想要一个协变list,他们不写List﹤Fruit>,而是写List﹤? extends Fruit>。所以这是一个通配符。问题是,这是用户代码。这些用户通常都没有类库设计人员那么专业。此外,这些注释间一个单一的不匹配将会带来类型错误。因此,难怪你会得到大量与通配符有关的非常棘手的错误信息,我认为这是Java泛型最重要的罪魁祸首。因为这种通配符的方法对于普通人来说确实是太复杂、太难于处理。 

可变性是当你结合泛型和子类型时非常重要的东西,但它也很复杂。没有办法能完全让它变成一件小事。我们做的比Java好的地方是,可以让你只在类库中做一次,使得用户不需要考虑和处理它。 

抽象类

Bill Venners:在Scala中,一个类型可以是另一个类型的成员,就如同方法和域可以是一个类型的成员。在Scala中,这些类型成员可以是抽象的,就如同在Java中方法可以抽象。在抽象类型成员和泛型参数之间是否存在重叠?为什么Scala两者都包含?抽象类型具有哪些泛型所不具有的功能?

Martin Odersky:抽象类型确实具有一些泛型所不具有的功能,但首先让我陈述一个稍微普遍的原理。一直都存在两个抽象概念:参数和抽象成员。在Java中,两者都有,但它取决于你在抽象什么。在Java中你可以有抽象方法,但你不能把方法作为参数传递。你并不拥有抽象域,但可以传值作为参数。同样,你没有抽象类型成员,但你可以指定一种类型作为参数。因此,在Java中你可以有以上3种方式,但使用什么抽象原则是有区别的。你可以争辩说,这种区别是相当武断的。 

我们在Scala中所做的是力求更全面和垂直。我们决定对以上所有3种成员都采用同样的构造原则。所以,你可以有抽象域,也可以有值参数。你可以传递方法(或“函数”)作为参数,或者也可以抽象它们。您可以指定类型作为参数,或者也可以抽象它们。我们概念性地得到的是,我们可以按照其它的建模另一个。至少在原则上,我们可以表达各种参数为一种面向对象的抽象。因此,在某种意义上可以说Scala是一种更垂直、更全面的语言。 

现在,问题仍然存在,这能给你带来什么好处?抽象类型是对以上我们谈到的问题的很好的处理,一个已经存在了很长一段时间的标准问题是动物和食物。让人不解的是,有个动物类,带有一个吃一些食物的方法。问题是,如果我们建立一个动物类的子类,如牛,那么它们将只吃草,而不是任意食物。例如,牛不会吃鱼。你真正想要的是一个牛类,带有一个只吃草而不吃其它东西的方法。实际上,在Java中你不能这样做,如像前面提到的分配一个任意的水果给苹果变量的问题。 

问题是,你怎么办?答案是,你为动物类添加一个抽象类型。你说,新的动物类中含有一个SuitableFood(适当食物)的类型,这我不知道。因此这是一个抽象类型。你并不给出类型实现。然后,你就可以有一个吃的方法,只吃适当的食物。然后在牛类中,我会说,好吧,我有一个牛类,它继承于动物类,并且对于牛类型来说,适当的食物就是草。因此,在子类中就可以实现这些抽象。 

现在,你可以说,我可以用参数完成同样的事情。事实上,你确实可以。你可以给动物类添加参数,参数为各种所吃的食物。但在实践中,当你要完成很多事情的时候,这就导致了参数爆炸,而且通常更重要的是,参数的范围。在1998年的ECOOP ,Kim Bruce, Phil Wadler和我一起发过一个文章,我们指出,随着你增加你所不知道的东西的数量,典型的程序将会以2次方程式的数量增加。因此,我们有理由尽量不用参数,而是使用抽象成员。 




赞(0)    操作        顶端 
总帖数
1
每页帖数
101/1页1
返回列表
发新帖子
请输入验证码: 点击刷新验证码
您需要登录后才可以回帖 登录 | 注册
技术讨论