想象一下这个简单的特征Value
,其中每个实现类都有一个类型为value
的T
。
trait Value {
type T
def value: T
}
我们有两个不同的实现类,分别表示Int
和String
值。
case class IntValue(override val value: Int) extends Value {
override type T = Int
}
case class StringValue(override val value: String) extends Value {
override type T = String
}
如果我们有List
值,我们希望有一种类型安全的方式来选择特定类型的所有值。类Values
及其伴随对象可以帮助我们做到这一点:
object Values {
private type GroupedValues = Map[ClassTag[_ <: Value], List[Value]]
def apply(values: List[Value]): Values = {
val groupedValues: GroupedValues = values.groupBy(value => ClassTag(value.getClass))
new Values(groupedValues)
}
}
class Values private (groupedValues: Values.GroupedValues) {
// Get a List of all values of type V.
def getValues[V <: Value : ClassTag] = {
val classTag = implicitly[ClassTag[V]]
groupedValues.get(classTag).map(_.asInstanceOf[List[V]]).getOrElse(Nil)
}
def getValue[V <: Value : ClassTag] = {
getValues.head
}
def getValueOption[V <: Value : ClassTag] = {
getValues.headOption
}
def getValueInner[V <: Value : ClassTag] = {
getValues.head.value
}
}
所有这些在Scala 2.13和Dotty 0.20.0-RC1中都可以正常工作,因此具有混合值列表...
val valueList = List(IntValue(1), StringValue("hello"))
val values = Values(valueList)
…我们可以选择元素并将其返回为正确的类型-在编译时全部检查:
val ints: List[IntValue] = values.getValues[IntValue]
val strings: List[StringValue] = values.getValues[StringValue]
val int: IntValue = values.getValue[IntValue]
val string: StringValue = values.getValue[StringValue]
val intOption: Option[IntValue] = values.getValueOption[IntValue]
val stringOption: Option[StringValue] = values.getValueOption[StringValue]
val i: Int = values.getValueInner[IntValue]
val s: String = values.getValueInner[StringValue]
Option[T]
作为值失败但是,如果我们添加此函数以选择值作为其T
类型(即Int
和String
),并将其作为Option
返回,]]]
class Values ... { ... def getValueInnerOption[V <: Value : ClassTag] = { getValues.headOption.map(_.value) } }
…然后在Scala 2.13中一切正常:
val iOption: Option[Int] = values.getValueInnerOption[IntValue] val sOption: Option[String] = values.getValueInnerOption[StringValue]
但是在Dotty 0.20.0-RC1中,它不会编译:
-- [E007] Type Mismatch Error: getValue.scala:74:29 74 | val iOption: Option[Int] = values.getValueInnerOption[IntValue] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Found: Option[Any] | Required: Option[Int] -- [E007] Type Mismatch Error: getValue.scala:75:32 75 | val sOption: Option[String] = values.getValueInnerOption[StringValue] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | Found: Option[Any] | Required: Option[String]
我们可以通过在
getValueInnerOption
中添加类型参数来解决该问题,该参数将返回类型和抽象类型T
绑定在一起,并允许我们指定返回类型。
def getValueInnerOption[V <: Value {type T = U} : ClassTag, U]: Option[U] = { getValues.headOption.map(_.value) }
不幸的是,这意味着我们将不得不在呼叫站点添加
T
的实际类型(即Int
或String
),这很遗憾,因为它只是样板。
val iOption: Option[Int] = values.getValueInnerOption[IntValue, Int] val sOption: Option[String] = values.getValueInnerOption[StringValue, String]
Dotty中的错误或怎么办?
似乎Dotty已经知道T
的上限,但无法将该知识传播到函数的结果类型。如果尝试从String
寻求IntValue
,则可以看到此信息:
-- [E057] Type Mismatch Error: getValue.scala:75:39 75 | val wtf = values.getValueInnerOption[IntValue, String] | ^ |Type argument IntValue does not conform to upper bound Value{T = String}
原始代码(没有类型参数
U
)是否可以在最终的Scala 3.0中使用,或者是否需要以其他方式编写?
一个简单的值层次结构,想象一下这个简单的特征值,其中每个实现类都有一个类型T的值。特征值{类型T def值:T}我们有两个不同的实现...
val iOption: Option[Int] = values.getValueInnerOption[IntValue]
val sOption: Option[String] = values.getValueInnerOption[StringValue]
编译。
但是问题是我不确定(和getValueInner
)
应该是否有效。因为它们的推断返回类型涉及V#T
(如果您输入错误的返回类型,则可以在错误消息中看到它们),并尝试显式指定它们会给出V不是合法路径,因为它不是具体类型
(请参阅http://dotty.epfl.ch/docs/reference/dropped-features/type-projection.html)