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

为何Shapeless的Aux模式可用,而我的实现却失效?

问题:自定义TupleProvider无法正确推导Repr类型,得到Nothing

我尝试实现一个从case类生成编译期元组类型的工具,认为实现和Shapeless的Generic一致:

import scala.reflect.macros.whitebox
import scala.language.experimental.macros

class TupleProvider[T <: Product] {
  type Repr
  def provide(t: T): Repr = ???
}

object TupleProvider {
  type Aux[T <: Product, R] = TupleProvider[T] {type Repr = R}
  def apply[T <: Product](implicit tp: TupleProvider[T]): Aux[T, tp.Repr] = tp
  implicit def materialize[T, R]: Aux[T, R] = macro TypeProviderMacros.repr1[T, R]

  def instance[T <: Product, R <: Product]: Aux[T, R] = new TupleProvider[T] { type Repr = R }
}

class TypeProviderMacros(val c: whitebox.Context) {
  import c.universe._
  def repr1[T: WeakTypeTag, R: WeakTypeTag] = {
    q"TupleProvider.instance[${weakTypeOf[T]}, ${typeOf[Tuple2[String, Int]]}]"
  }
}

但执行以下代码时:

case class Foo(x: Int, y: String)
val p = TupleProvider[Foo]

p的类型最终是TupleProvider.Aux[Foo, Nothing],而Shapeless的Generic却能正确推导Aux[Foo, Int :: String :: HNil]类型,请问问题出在哪里?


问题分析与解决

核心问题

  1. 未绑定的类型参数R:你的materialize方法声明了implicit def materialize[T, R]: Aux[T, R],这里的R没有任何约束或推导逻辑,编译器无法确定它的具体类型,默认会填充为Nothing。
  2. 宏实现未关联类型参数:宏里硬编码了Tuple2[String, Int],既没有根据输入T的字段生成对应元组类型,也没有把生成的类型和方法的R参数绑定,导致编译器无法捕获到正确的Repr类型。

Shapeless的Generic之所以能工作,是因为它的宏会根据输入的case类T动态生成对应的Repr类型(比如HList),并在宏展开时将这个类型注入到Aux类型中,让编译器能推导到具体的Repr。

修正后的代码

调整materialize方法的签名,让宏根据T的字段自动生成对应元组类型,并正确绑定到Repr:

import scala.reflect.macros.whitebox
import scala.language.experimental.macros

class TupleProvider[T <: Product] {
  type Repr
  def provide(t: T): Repr = ???
}

object TupleProvider {
  type Aux[T <: Product, R] = TupleProvider[T] {type Repr = R}
  def apply[T <: Product](implicit tp: TupleProvider[T]): Aux[T, tp.Repr] = tp

  // 移除未绑定的R参数,让宏推导并返回正确的Aux类型
  implicit def materialize[T <: Product]: Aux[T, _] = macro TypeProviderMacros.repr1[T]

  def instance[T <: Product, R]: Aux[T, R] = new TupleProvider[T] { type Repr = R }
}

class TypeProviderMacros(val c: whitebox.Context) {
  import c.universe._
  def repr1[T: WeakTypeTag]: Tree = {
    val tpe = weakTypeOf[T]
    // 提取case类的所有字段类型(只保留case访问器方法的返回类型)
    val fieldTypes = tpe.decls.collect {
      case m: MethodSymbol if m.isCaseAccessor => m.returnType
    }.toList

    // 根据字段数量生成对应元组类型
    val tupleType = fieldTypes.size match {
      case 0 => typeOf[Unit] // 空case类对应Unit
      case n => appliedType(definitions.TupleClass(n), fieldTypes)
    }

    // 生成绑定了正确T和tupleType的instance调用
    q"TupleProvider.instance[${tpe}, ${tupleType}]"
  }
}

测试验证

case class Foo(x: Int, y: String)
val p = TupleProvider[Foo]
// 此时p的类型为TupleProvider.Aux[Foo, (Int, String)],符合预期

内容的提问来源于stack exchange,提问作者Dima

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 05:20:12