You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Dotty无法推断含抽象类型泛型Scala函数的返回类型问题

Scala 3 (Dotty) 中类型安全值选择的类型推断问题

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] or Option[String] as expected.
  • But in Dotty 0.20.0-RC1, it fails with type mismatch errors, inferring Option[Any] instead of the specific T type tied to V.

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:05:33