【问题标题】:How to avoid ambiguous conversion chains with multiple Type Class relationships?如何避免具有多个类型类关系的模棱两可的转换链?
【发布时间】:2015-10-19 21:37:20
【问题描述】:

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

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

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

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

例如,由于(Int, Int)Int 类型上的Vector,而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] = ...
}

在您尝试使用元组类型 (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 原则。当我为AbelianGroups 添加新实现时,我必须在两个伴随对象中实现转换,就像我为Numeric 和OtherTypeX 等所做的那样。所以,我觉得我在某个地方走错了方向.

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

【问题讨论】:

  • 为什么需要fromAbelianGroupAbelianGroup[T] 已经是 Monoid[T],编译器将在任何需要 Monoid[T] 的地方提供一个。
  • 因为编译器会在 Monoid 的伴生类中寻找可能的转换,所以如果没有 fromAbelianGroup,它将找不到 fromNumberic 等的转换。另外,如果我在每个调用站点导入 AbelianGroup 的伴生对象,这样它找到转换(我真的不认为这是一个令人满意的解决方案),即使没有 fromAbelianGroup,歧义仍然存在。
  • 你能用无法编译的代码(v1)发布一个要点吗?

标签: scala typeclass context-bound


【解决方案1】:

你可以move the implicits for which you want to have lower priority into a supertype of the companion object:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-10-03
    • 2014-06-01
    • 2014-11-26
    • 1970-01-01
    • 1970-01-01
    • 2020-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多