在什么情况下应该优先使用抽象类型而不是类型参数?
nukf8bse1#
为了补充我之前关于抽象类型与参数的答案,你还有JESSE EICHAR's recent blog post(2010年5月3日)强调了一些关键的区别:
trait C1[A] { def get : A def doit(a:A):A } trait C2 { type A def get : A def doit(a:A):A }
在C2情况下,参数是“隐藏”的(作为内部抽象类型)。(除了,正如retronym所说,它实际上并没有被埋葬,见下文)而对于泛型类型,参数被显式地提到,帮助其他表达式知道它们应该使用什么类型因此(C1:参数):
C2
//compiles def p(c:C1[Int]) = c.doit(c.get)
它会编译,但您会显式公开要使用的'A'类型。和(C2:抽象类型):
A
// doesn't compile def p2(c:C2) = c.doit(c.get) <console>:6: error: illegal dependent method type def p2(c:C2) = c.doit(c.get) ^
它不会编译,因为p2定义中从未提到'A',所以doit在编译类型时不知道它应该返回什么。当使用抽象类型 * 和 * 来避免任何“类型泄漏”到接口时(即想要暴露'A'实际上是什么),您可以指定一个非常通用的类型作为p2的返回值:
doit
// compiles because the internals of C2 does not leak out def p(c:C2):Unit = c.doit(c.get)
或者你可以直接在doit函数中“修复”这个类型:def doit(a:A):Int而不是def doit(a:A):A,这意味着:def p2(c:C2) = c.doit(c.get)将编译(即使p2没有提到任何返回类型)最后(retronym的注解),您可以通过细化C2抽象参数显式地指定A:
def doit(a:A):Int
def doit(a:A):A
def p2(c:C2) = c.doit(c.get)
scala> def p2(c:C2 { type A = Int }): Int = c.doit(c.get) p2: (c: C2{type A = Int})Int
或者添加一个类型参数(并使用它来细化C2抽象类型!)
scala> def p2[X](c:C2 { type A = X }): X = c.doit(c.get) p2: [X](c: C2{type A = X})X
所以建议抽象:
*当你想从客户端代码中隐藏类型成员的确切定义时,使用像C2中的抽象类型(但要注意使用C2的函数定义)*当你想在C2的子类中覆盖类型协变时,使用抽象类型(有界类型抽象)*当你想通过traits混合这些C2类型的定义时,使用抽象类型(当你将C2与你的类混合时,你不会有'A'来处理:你只混合C2)
对于其他需要简单类型示例化的情况,请使用参数。(if你知道不需要扩展,但你仍然需要处理几种类型:这就是参数类型的用途)retronym添加:
主要区别*方差:C2只能在A中不变,*类型成员在子类型中被选择性覆盖的方式(而类型参数必须重新声明并传递给超类型)
(as illustrating here:
trait T1 { type t val v: t } trait T2 extends T1 { type t <: SomeType1 } trait T3 extends T2 { type t <: SomeType2 // where SomeType2 <: SomeType1 } class C extends T3 { type t = Concrete // where Concrete <: SomeType2 val v = new Concrete(...) }
Dmytro Mitin在评论中添加了2023:
方差:C2只能在A中保持不变
这并不完全正确。只有C1的变量可以在定义位置和调用位置声明,而C2的变量只能在调用位置声明(如Java中的类型参数)参见“Scala: Abstract types vs generics”“在子类型中有选择地重写类型成员的方式”这也不是区别。
C1
trait T1[t] trait T2[t <: SomeType1] extends T1[t] trait T3[t <: SomeType2] extends T2[t] class C extends T3[Concrete]
在2.10+ def p2(c:C2) = c.doit(c.get)编译。参见this scastie snippet。
1条答案
按热度按时间nukf8bse1#
为了补充我之前关于抽象类型与参数的答案,你还有JESSE EICHAR's recent blog post(2010年5月3日)强调了一些关键的区别:
在
C2
情况下,参数是“隐藏”的(作为内部抽象类型)。(除了,正如retronym所说,它实际上并没有被埋葬,见下文)
而对于泛型类型,参数被显式地提到,帮助其他表达式知道它们应该使用什么类型
因此(C1:参数):
它会编译,但您会显式公开要使用的'
A
'类型。和(C2:抽象类型):
它不会编译,因为p2定义中从未提到'
A
',所以doit
在编译类型时不知道它应该返回什么。当使用抽象类型 * 和 * 来避免任何“类型泄漏”到接口时(即想要暴露'
A
'实际上是什么),您可以指定一个非常通用的类型作为p2的返回值:或者你可以直接在
doit
函数中“修复”这个类型:def doit(a:A):Int
而不是def doit(a:A):A
,这意味着:def p2(c:C2) = c.doit(c.get)
将编译(即使p2没有提到任何返回类型)最后(retronym的注解),您可以通过细化C2抽象参数显式地指定
A
:或者添加一个类型参数(并使用它来细化C2抽象类型!)
所以建议抽象:
*当你想从客户端代码中隐藏类型成员的确切定义时,使用像
C2
中的抽象类型(但要注意使用C2
的函数定义)*当你想在
C2
的子类中覆盖类型协变时,使用抽象类型(有界类型抽象)*当你想通过traits混合这些
C2
类型的定义时,使用抽象类型(当你将C2
与你的类混合时,你不会有'A
'来处理:你只混合C2
)对于其他需要简单类型示例化的情况,请使用参数。
(if你知道不需要扩展,但你仍然需要处理几种类型:这就是参数类型的用途)
retronym添加:
主要区别
*方差:
C2
只能在A
中不变,*类型成员在子类型中被选择性覆盖的方式(而类型参数必须重新声明并传递给超类型)
(as illustrating here:
Dmytro Mitin在评论中添加了2023:
方差:
C2
只能在A
中保持不变这并不完全正确。
只有
C1
的变量可以在定义位置和调用位置声明,而C2
的变量只能在调用位置声明(如Java中的类型参数)参见“Scala: Abstract types vs generics”
“在子类型中有选择地重写类型成员的方式”
这也不是区别。
在2.10+
def p2(c:C2) = c.doit(c.get)
编译。参见this scastie snippet。