在我的库中,我有三个类型类:
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上的向量,并且类型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类对象:
Monoid.fromAbelianGroup(AbelianGroup.fromVector(Vector.from2Tuple, AbelianGroup.fromNumeric))
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]]
}