如何让Scala 3联合类型的Typeclass隐式实例正确解析?
问题描述
定义如下类型类及基础实例:
trait ExampleTypeClass[A] // 省略若干方法 object ExampleTypeClass: given ExampleTypeClass[Int] = new ExampleTypeClass{/* ... */} given ExampleTypeClass[Boolean] = new ExampleTypeClass{/* ... */}
已知若存在A和B的类型类实例,可实现A | B类型的实例(A | B与B | A类型等价,逻辑与顺序无关)。尝试实现联合类型的实例:
given unionExampleTypeClass[A, B]( using a: ExampleTypeClass[A], b: ExampleTypeClass[B] ): ExampleTypeClass[A | B] = new ExampleTypeClass[A | B]{ // 依赖a和b的逻辑(与顺序无关) }
显式调用可正常编译:
val intOrBoolTC1: ExampleTypeClass[Int | Boolean] = unionExampleTypeClass[Int, Boolean] val intOrBoolTC2: ExampleTypeClass[Int | Boolean] = unionExampleTypeClass[Boolean, Int]
但隐式解析失败,报错歧义:
val intOrBoolTC3 = summon[ExampleTypeClass[Int | Boolean]] // 报错信息: //[error] Ambiguous given instances: both given instance given_ExampleTypeClass_Int in object ExampleTypeClass and given instance given_ExampleTypeClass_Boolean in object ExampleTypeClass match type ExampleTypeClass[A] of parameter x of method summon in object Predef // [error] val intOrBoolTC3 = summon[ExampleTypeClass[Int | Boolean]] // [error] ^
核心问题:unionExampleTypeClass[Int, Boolean]与unionExampleTypeClass[Boolean, Int]均生成ExampleTypeClass[Int | Boolean]实例,导致隐式解析歧义。
解决方案
方法一:约束类型顺序避免重复候选
通过编译时约束强制A和B的类型名称按字典序排序,确保同一联合类型仅能匹配一个given实例:
import scala.compiletime.* // 辅助方法:获取编译时类型的字符串名称 inline def typeName[T]: String = constValue[Type[T]] // 约束:A的类型名称字典序小于等于B type ABeforeB[A, B] = (typeName[A] <:< typeName[B]) =:= true trait ExampleTypeClass[A] // 省略方法 object ExampleTypeClass: given ExampleTypeClass[Int] = new ExampleTypeClass[Int]{/* ... */} given ExampleTypeClass[Boolean] = new ExampleTypeClass[Boolean]{/* ... */} // 添加ABeforeB约束,确保A和B顺序唯一 given unionExampleTypeClass[A, B]( using a: ExampleTypeClass[A], b: ExampleTypeClass[B], ev: ABeforeB[A, B] ): ExampleTypeClass[A | B] = new ExampleTypeClass[A | B]{ // 依赖a和b的逻辑 }
此时,summon[ExampleTypeClass[Int | Boolean]]仅会匹配符合字典序约束的given实例,不会产生歧义。
方法二:利用Sum Mirror自动推导联合类型实例
联合类型本质是Scala 3的Sum类型,可通过Mirror.SumOf自动生成实例,彻底避免顺序问题,同时支持多元素联合类型:
import scala.deriving.Mirror import scala.compiletime.* trait ExampleTypeClass[A]: // 示例方法,根据实际需求调整 def handle(x: A): String object ExampleTypeClass: // 基础实例 given ExampleTypeClass[Int] with override def handle(x: Int): String = s"Int: $x" given ExampleTypeClass[Boolean] with override def handle(x: Boolean): String = s"Boolean: $x" // 为任意Sum类型(包括联合类型)自动生成实例 inline given [T](using m: Mirror.SumOf[T]): ExampleTypeClass[T] = new ExampleTypeClass[T]: override def handle(x: T): String = // 按Sum类型的元素顺序匹配处理 inline m.ordinal(x) match case idx => val elemType = m.MirroredElemTypes[idx] summonInline[ExampleTypeClass[elemType]] .handle(x.asInstanceOf[elemType])
这种方法扩展性更强,无需手动处理类型顺序,可自动适配任意数量元素的联合类型。
内容的提问来源于stack exchange,提问作者MartinHH
相关产品推荐
相关产品推荐

