Dotty 无法推断采用抽象类型的类型参数特征的泛型 Scala 函数的结果类型
Dotty cannot infer result type of generic Scala function taking type parameter trait with abstract type
一个简单的价值层次结构
想象一下这个简单的特征 Value
,其中每个实现 class 都有一个 value
某种类型 T
。
trait Value {
type T
def value: T
}
我们有两个不同的实现 classes 分别代表 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
}
类型安全select值的离子
如果我们有 List
个值,我们希望有一种类型安全的方法来 selecting 特定类型的所有值。 Class 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)
…我们可以 select 元素并将它们 return 编辑为正确的类型——全部在编译时检查:
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]
在 Dotty
中选择一个值作为 Option[T]
失败
但是,如果我们将此函数添加到 select 值作为它们的 T
类型(即 Int
和 String
)并得到它 returned作为 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
添加一个类型参数来解决这个问题,它将 return 类型和抽象类型 T
联系在一起,并允许我们指定 return类型。
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
的上限是什么,但无法将该知识传播到函数的结果类型。如果尝试从 IntValue
:
中请求 String
,则可以看到这一点
-- [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 中运行,还是需要以不同的方式编写?
_.value
有一个 dependent function type 默认情况下不会推断,但您可以指定它:
def getValueInnerOption[V <: Value : ClassTag] = {
getValues.headOption.map((_.value): (v: V) => v.T)
}
然后
val iOption: Option[Int] = values.getValueInnerOption[IntValue]
val sOption: Option[String] = values.getValueInnerOption[StringValue]
编译。
但问题是我不确定它(和 getValueInner
)应该 工作。因为它们的推断 return 类型涉及 V#T
(如果您输入错误的 return 类型,您可以在错误消息中看到它们),并且尝试明确指定它们会给出
V is not a legal path since it is not a concrete type
(参见 http://dotty.epfl.ch/docs/reference/dropped-features/type-projection.html)
在 Dotty 中尝试 match types 作为类型投影的替代品
type InnerType[V <: Value] = V match {
case IntValue => Int
case StringValue => String
}
trait Value {
type This >: this.type <: Value
type T = InnerType[This]
def value: T
}
case class IntValue(override val value: Int) extends Value {
override type This = IntValue
}
case class StringValue(override val value: String) extends Value {
override type This = StringValue
}
def getValueInner[V <: Value { type This = V } : ClassTag]: InnerType[V] = {
getValues.head.value
}
def getValueInnerOption[V <: Value { type This = V } : ClassTag]: Option[InnerType[V]] = {
getValues.headOption.map(_.value)
}
一个简单的价值层次结构
想象一下这个简单的特征 Value
,其中每个实现 class 都有一个 value
某种类型 T
。
trait Value {
type T
def value: T
}
我们有两个不同的实现 classes 分别代表 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
}
类型安全select值的离子
如果我们有 List
个值,我们希望有一种类型安全的方法来 selecting 特定类型的所有值。 Class 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)
…我们可以 select 元素并将它们 return 编辑为正确的类型——全部在编译时检查:
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]
在 Dotty
中选择一个值作为Option[T]
失败
但是,如果我们将此函数添加到 select 值作为它们的 T
类型(即 Int
和 String
)并得到它 returned作为 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
添加一个类型参数来解决这个问题,它将 return 类型和抽象类型 T
联系在一起,并允许我们指定 return类型。
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
的上限是什么,但无法将该知识传播到函数的结果类型。如果尝试从 IntValue
:
String
,则可以看到这一点
-- [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 中运行,还是需要以不同的方式编写?
_.value
有一个 dependent function type 默认情况下不会推断,但您可以指定它:
def getValueInnerOption[V <: Value : ClassTag] = {
getValues.headOption.map((_.value): (v: V) => v.T)
}
然后
val iOption: Option[Int] = values.getValueInnerOption[IntValue]
val sOption: Option[String] = values.getValueInnerOption[StringValue]
编译。
但问题是我不确定它(和 getValueInner
)应该 工作。因为它们的推断 return 类型涉及 V#T
(如果您输入错误的 return 类型,您可以在错误消息中看到它们),并且尝试明确指定它们会给出
V is not a legal path since it is not a concrete type
(参见 http://dotty.epfl.ch/docs/reference/dropped-features/type-projection.html)
在 Dotty 中尝试 match types 作为类型投影的替代品
type InnerType[V <: Value] = V match {
case IntValue => Int
case StringValue => String
}
trait Value {
type This >: this.type <: Value
type T = InnerType[This]
def value: T
}
case class IntValue(override val value: Int) extends Value {
override type This = IntValue
}
case class StringValue(override val value: String) extends Value {
override type This = StringValue
}
def getValueInner[V <: Value { type This = V } : ClassTag]: InnerType[V] = {
getValues.head.value
}
def getValueInnerOption[V <: Value { type This = V } : ClassTag]: Option[InnerType[V]] = {
getValues.headOption.map(_.value)
}