Dotty无法推断含抽象类型泛型Scala函数的返回类型问题
Let's break down this issue and walk through what's happening with Scala 3's type inference, plus better ways to handle it.
First, Recap the Setup
We start with a simple trait hierarchy for typed values:
trait Value { type T def value: T } 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 }
And a Values utility to group and retrieve values in a type-safe way (works in both Scala 2.13 and early Dotty builds):
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) { 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 }
For most methods, this works seamlessly—we get compile-time checked, correctly typed results:
val valueList = List(IntValue(1), StringValue("hello")) val values = Values(valueList) val ints: List[IntValue] = values.getValues[IntValue] val int: IntValue = values.getValue[IntValue] val i: Int = values.getValueInner[IntValue]
The Problem: getValueInnerOption in Dotty
When we add this method to retrieve the inner value wrapped in an Option:
def getValueInnerOption[V <: Value : ClassTag] = { getValues.headOption.map(_.value) }
- It works perfectly in Scala 2.13, returning
Option[Int]orOption[String]as expected. - But in Dotty 0.20.0-RC1, it fails with type mismatch errors, inferring
Option[Any]instead of the specificTtype tied toV.
We can fix it by adding a second type parameter to bind V's T type explicitly:
def getValueInnerOption[V <: Value {type T = U} : ClassTag, U]: Option[U] = { getValues.headOption.map(_.value) }
But this forces us to specify both types at the call site, which is redundant boilerplate:
val iOption: Option[Int] = values.getValueInnerOption[IntValue, Int]
Your Question: Will the Original Code Work in Final Scala 3.0?
Unfortunately, the unmodified getValueInnerOption won't work as-is in the final Scala 3.0. The root issue is that Scala 3's type inference engine handles dependent types more strictly than Scala 2. In Scala 2, the compiler could implicitly track the link between V and its T type through the map call, but Scala 3 needs explicit hints to make that connection.
That said, there are cleaner alternatives that avoid the redundant type parameters.
Better Fixes for Scala 3
Option 1: Explicitly Type the Lambda Parameter
The simplest fix is to annotate the lambda parameter in map as V—this tells Scala 3 to use V's specific T type instead of falling back to the raw Value trait's T (which resolves to Any):
def getValueInnerOption[V <: Value : ClassTag]: Option[V#T] = { getValues.headOption.map((v: V) => v.value) }
Now you can call it exactly like you did in Scala 2, no extra type parameters needed:
val iOption: Option[Int] = values.getValueInnerOption[IntValue] val sOption: Option[String] = values.getValueInnerOption[StringValue]
Option 2: Use a Type Class for Dependent Type Resolution
If you prefer a more declarative approach, you can define a helper type class to capture the relationship between V and its T type:
trait ValueType[V <: Value] { type Out } object ValueType { type Aux[V <: Value, O] = ValueType[V] { type Out = O } implicit def valueType[V <: Value]: ValueType.Aux[V, V#T] = new ValueType[V] { type Out = V#T } }
Then update the method to use this type class:
def getValueInnerOption[V <: Value : ClassTag : ValueType]: Option[ValueType[V]#Out] = { getValues.headOption.map(_.value) }
This also lets you call the method without redundant type arguments, and it's more extensible if you need to add more complex type logic later.
Why the Error Happened (And Why Your Test Worked)
As you noticed, Scala 3 does know the upper bound for T—when you tried to call getValueInnerOption[IntValue, String], it correctly errored because IntValue's T isn't String. The issue was just that the inference engine needed a nudge to connect the V type to its specific T in the map call.
内容的提问来源于stack exchange,提问作者mgd

