我有一些类型类,例如:
trait ExampleTypeClass[A]
// imagine one or more methods here
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]{
// imagine some logic that depends on a and b here
// (which does not depend on the order of a and b)
}
当这样实现时,我可以显式地调用它 - 以下编译正常:
val intOrBoolTC1: ExampleTypeClass[Int | Boolean] =
unionExampleTypeClass[Int, Boolean]
val intOrBoolTC2: ExampleTypeClass[Int | Boolean] =
unionExampleTypeClass[Boolean, Int]
但是,隐式解析失败:
val intOrBoolTC3 = summon[ExampleTypeClass[Int | Boolean]]
// results in:
//[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] ^
有没有办法使
A | B
的组合实现在隐式范围内可用,以便调用此类实例可以工作?
我还尝试了
inline
和 summonInline
的一些变体,但这些并没有解决主要问题(据我所知)似乎是 unionExampleTypeClass[Int, Boolean]
和 unionExampleTypeClass[Boolean, Int]
都返回所需的类型,导致含糊不清。
我发现了一种相关问题Implicit conversion between a Union Type and Either in Scala 3,但除了证实我的怀疑根本问题可能是什么之外,我没有在那里找到答案。
为了解决歧义,您可以优先考虑类型类的实例
trait LowPriorityExampleTypeClass:
given ExampleTypeClass[Int] = null
object ExampleTypeClass extends LowPriorityExampleTypeClass:
given ExampleTypeClass[Boolean] = null
您可以尝试在Scala 2中使用类似于
shapeless.Lub
的类型类https://github.com/milessabin/shapeless/blob/main/core/shared/src/main/scala/shapeless/typeoperators.scala#L186 -L202 Scala 中 3 种类型之间的协方差
trait Lub[-A, -B, Out]:
def left(a: A): Out
def right(b: B): Out
object Lub:
given [T]: Lub[T, T, T] with
def left(a: T): T = a
def right(b: T): T = b
given unionExampleTypeClass[A, B, Out](using
ExampleTypeClass[A],
ExampleTypeClass[B],
Lub[A, B, Out],
): ExampleTypeClass[Out] = null
但是不仅
summon[ExampleTypeClass[Int | Boolean]]
会编译,例如 summon[ExampleTypeClass[Any]]
也会
https://scastie.scala-lang.org/DmytroMitin/680pTCXQQkqRxaXa2V6ldQ/5
如果你想禁止
ExampleTypeClass[Any]
等,你可以添加约束NotGiven[Out =:= Any]
,NotGiven[Out =:= AnyVal]
,NotGiven[Out =:= AnyRef]
:
given unionExampleTypeClass[A, B, Out](using
ExampleTypeClass[A],
ExampleTypeClass[B],
Lub[A, B, Out],
NotGiven[Out =:= Any],
NotGiven[Out =:= AnyVal],
NotGiven[Out =:= AnyRef],
): ExampleTypeClass[Out] = null
https://scastie.scala-lang.org/DmytroMitin/680pTCXQQkqRxaXa2V6ldQ/8
但是仍然会定义类型类的实例,而不仅仅是为
Int | Boolean
。例如,如果您引入抽象 type T >: Int | Boolean
,summon[ExampleTypeClass[T]]
也会编译。