如何避免具有多个类型类关系的模糊转换链



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

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] {
  ...
}

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

  • 如果类型Tscala.math.Numeric类型,则它也是AbelianGroup类型。
  • 如果类型TAbelianGroup,它也是Monoid(目前,AbelianGroup扩展Monoid,但不一定是这样)
  • 如果类型T是类型U上的Vector,并且类型U是Monoid,则类型T也是Monoid
  • 如果类型T是类型U上的向量,并且类型U是AbelianGroup,则T也是AbelianGroup

例如,由于(Int, Int)是类型Int上的Vector,并且Int是一个abelangroup,那么(Int, Int)也是一个abelangroup。

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

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] = ...
}

这很好,直到你尝试使用像(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] = ...
}

然而,这有点令人不满意,因为它本质上违反了DRY原则。当我为AbelianGroup s添加新的实现时,我必须在两个伙伴对象中实现转换,就像我为Numeric和OtherTypeX所做的那样。所以,我觉得我好像在某个地方拐错了弯。

是否有一种方法来修改我的代码,以避免这种冗余和解决编译时歧义错误?这种情况下的最佳实践是什么?

您可以将您希望具有较低优先级的隐式移动到伴随对象的超类型中:

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]]
}

最新更新