Scala:从泛型集合中检索类型参数(ClassTag?)

时间:2015-04-15 19:07:41

标签: scala generics types type-inference

我正在定义一个带有类型参数的抽象类,以及设置此类型的几个具体类。

abstract class GenericFoo[T: ClassTag] {
    def defaultValue: T
    def useTheValue(v: T): T
}
object DoubleFoo extends GenericFoo[Double] {
    def defaultValue = 42.0
    def useTheValue(v: Double) = v * 2
}
object IntFoo extends GenericFoo[Int] {
    def defaultValue = 1337
    def useTheValue(v: Int) = v + 64
}

但是当我将我的Foos存储在一个混合集合中时,我的类型参数T似乎总是最终被推断为Any

val fooCollection:List[GenericFoo[_]] = List(DoubleFoo, IntFoo)

for {
    foo <- fooCollection
    defaultValue = foo.defaultValue
    result = foo.useTheValue(defaultValue)
    // Error: `defaultValue` has type `Any`, cannot call `useTheValue`

} yield result

根据类似问题的几个答案,我想使用类型参数通配符(GenericFoo[_]),也许ClassTag可以帮助保存类型信息,但我无法让这个例子工作,因为我想要的。

在上面的代码段中,我希望foo.defaultValue被识别为具有适用于T的正确类型(foo.useTheValue())。我错过了什么吗?


编辑:@ gzm0提示建议我使用抽象类型成员。我原来的用例更多一些,因为我也为一个伴侣对象定义了一个抽象类型。我的实际代码类似于:

trait GenericCompanion[T] {
    // Some "static" members
    def defaultValue: T
    def useTheValue(v: T): T
}

abstract class GenericFoo[T] {
    def getCompanion: GenericCompanion[T]

    // Because of this operation, it's important that we have the same type `T`
    def useCompanion: T = getCompanion.useTheValue(theActualValue)

    val theActualValue: T
}

object ConcreteCompanion[Double] extends GenericCompanion[Double] {
    // ...
}
object ConcreteFoo[Double] extends GenericFoo[Double] {
    def getCompanion = ConcreteCompanion
}

GenericFoo[T]的每个具体实现都附带CompanionFoo[T]。应该实例化GenericFoo的具体子类,而CompanionFoo[T]对象在这里保存“静态”属性和操作。在此上下文中,GenericFoo中的类型参数与GenericCompanion中的类型参数相同非常重要。 (我希望这很清楚,如果我的例子令人费解,那就很抱歉!)

1 个答案:

答案 0 :(得分:1)

这是抽象类型成员的一个例子。试试这个:

abstract class GenericFoo {
    type V
    def defaultValue: V
    def useTheValue(v: V): V
}
object DoubleFoo extends GenericFoo {
    type V = Double
    def defaultValue = 42.0
    def useTheValue(v: Double) = v * 2
}
object IntFoo extends GenericFoo {
    type V = Int
    def defaultValue = 1337
    def useTheValue(v: Int) = v + 64
}

val fooCollection:List[GenericFoo] = List(DoubleFoo, IntFoo)

其余代码保持不变。

如果使用抽象类型成员(而不是类型参数),Scala将跟踪它对给定值知道的具体类型。

从你的例子中解释:

val foo: GenericCollection = ???
val defaultValue = foo.defaultValue
// defaultValue: foo.V
foo.useTheValue(defaultValue)
// foo.useTheValue: (foo.V): foo.V

虽然编译器不知道真实类型foo.V是什么,但它仍然可以确定类型签名匹配,并且使用useTheValue调用defaultValue是正常的。

据我所知,这也是一个完全可靠的推理类型参数,Scala只是不这样做。

<强>更新

即使使用了更为复杂的示例,您仍然可以这样做:

trait GenericCompanion {
    type T
    // Some "static" members
    def defaultValue: T
    def useTheValue(v: T): T
}

abstract class GenericFoo { self =>
    type T

    def getCompanion: GenericCompanion { type T = self.T }

    // Because of this operation, it's important that we have the same type `T`
    def useCompanion: T = getCompanion.useTheValue(theActualValue)

    val theActualValue: T
}

具体实施:

object ConcreteCompanionDouble extends GenericCompanion {
    type T = Double
    def defaultValue: Double = ???
    def useTheValue(v: Double): Double = ???
    // ...
}
object ConcreteFooDouble extends GenericFoo {
    type T = Double
    def getCompanion = ConcreteCompanionDouble
    val theActualValue: Double = ???
}