如何避免具有多个Type Class关系的模糊转换链?

Nim*_*and 7 scala typeclass context-bound

在我的库中,我有三个类型类:

trait Monoid[T] {
  val zero : T
  def sum(x : T, y : T) : T
}

trait AbelianGroup[T] extends Monoid[T] {
  def inverse(x : T) : T
  def difference(x : T, y : T) : T
}

//represents types that are represents lists with a fixed number of elements, such as
//the tuple type (Int, Int)
trait Vector[T, U] {
  ...
}
Run Code Online (Sandbox Code Playgroud)

在以下条件下,这些类型类可以相互转换:

  • 如果type T是一个scala.math.Numeric类型,它也是一个类型AbelianGroup.
  • 如果type T是a AbelianGroup,那么它也是一个Monoid(当前AbelianGroup扩展Monoid,但不一定是这种情况)
  • 如果type T是Vector over U型,而类型U是a Monoid,则type T也是a Monoid.
  • 如果类型T是类型U上的向量,并且类型U是a AbelianGroup,那么T也是一个AbelianGroup.

例如,既然(Int, Int)是Vector over类型Int,并且Int是AbelianGroup,那么(Int, Int)它也是AbelianGroup.

这些关系和其他关系很容易在伴侣类中实现,如下所示:

object Monoid {
  implicit def fromAbelianGroup[T : AbelianGroup] : Monoid[T] = implicitly[AbelianGroup[T]]
  implicit def fromVector[T : Vector[T, U], U : Monoid] : Monid[T] = ...
}

object AbelianGroup {
  implicit def fromNumeric[T : Numeric] : AbelianGroup[T] = ...
  implicit def fromOtherTypeX[T : ...] : AbelianGroup[T]
  ...
  implicit def fromVector[T : Vector[T, U], U : AbelianGroup] : AbelianGroup[T] = ...
}
Run Code Online (Sandbox Code Playgroud)

这很好用,直到你尝试使用类似元组的类型(Int, Int)作为Monoid.编译器找到两种方法来获得这种类型的Monoid类型类对象:

  1. Monoid.fromAbelianGroup(AbelianGroup.fromVector(Vector.from2Tuple, AbelianGroup.fromNumeric))

  2. Monoid.fromVector(Vector.from2Tuple, Monid.fromAbelianGroup(AbelianGroup.fromNumeric))

为了解决这种歧义,我修改了Monoid伴随类,以包含从Numeric(和其他可直接转换为AbelianGroup)的类型的直接转换.

/*revised*/
object Monoid {
  //implicit def fromAbelianGroup[T : AbelianGroup] : Monoid[T] = implicitly[AbelianGroup[T]]
  implicit def fromNumeric[T : Numeric] : Monoid[T] = ... //<-- redundant
  implicit def fromOtherTypeX[T : ...] : AbelianGroup[T] = ... //<-- redundant
  ...
  implicit def fromVector[T : Vector[T, U], U : Monoid] : Monid[T] = ...
}

object AbelianGroup {
  implicit def fromNumeric[T : Numeric] : AbelianGroup[T] = ...
  implicit def fromOtherTypeX[T : ...] : AbelianGroup[T] = ...
  ...
  implicit def fromVector[T : Vector[T, U], U : AbelianGroup] : AbelianGroup[T] = ...
}
Run Code Online (Sandbox Code Playgroud)

然而,这有点令人不满意,因为它基本上违反了DRY委托人.当我为AbelianGroups 添加新的实现时,我将不得不在两个伴随对象中实现转换,就像我已经完成的那样Numeric和OtherTypeX等等.所以,我觉得我在某个地方做了一个错误的转向.

有没有办法修改我的代码以避免这种冗余并解决编译时模糊错误?这种情况下的最佳做法是什么?

Ale*_*nov 0

您可以将想要降低优先级的隐式移至伴生对象的超类型中

trait LowPriorityMonoidImplicits {
  implicit def fromVector[T : Vector[T, U], U : Monoid] : Monoid[T] = ...
}

object Monoid extends LowPriorityMonoidImplicits  {
  implicit def fromAbelianGroup[T : AbelianGroup] : Monoid[T] = implicitly[AbelianGroup[T]]
}
Run Code Online (Sandbox Code Playgroud)