为何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]类型,请问问题出在哪里?
问题分析与解决
核心问题
- 未绑定的类型参数
R:你的materialize方法声明了implicit def materialize[T, R]: Aux[T, R],这里的R没有任何约束或推导逻辑,编译器无法确定它的具体类型,默认会填充为Nothing。 - 宏实现未关联类型参数:宏里硬编码了
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
相关产品推荐
相关产品推荐

