В моей библиотеке, у меня есть три класса типа:Как избежать неоднозначных цепей преобразования с несколькими отношениями типа класса?
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 и типа U являетсяMonoid
, то типT
также являетсяMonoid
. - Если тип T является вектором по типу U, а тип U является
AbelianGroup
, тоT
также являетсяAbelianGroup
.
Например, поскольку (Int, Int)
является вектором над типом 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] = ...
}
Это работает замечательно, пока вы не попытаетесь использовать что-то вроде типа кортежа (Int, Int)
как Monoid. Компилятор находит два способ получения типа объекта класса моноидного для такого типа:
Monoid.fromAbelianGroup(AbelianGroup.fromVector(Vector.from2Tuple, AbelianGroup.fromNumeric))
Monoid.fromVector(Vector.from2Tuple, Monid.fromAbelianGroup(AbelianGroup.fromNumeric))
Чтобы решить эту неоднозначность, я изменил класс компаньона моноидными к включают прямое преобразование из 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] = ...
}
Однако это немного неудовлетворительно, поскольку оно по существу нарушает принцип СУХОЙ. Когда я добавляю новые реализации для AbelianGroup
, мне нужно будет реализовать преобразование в обоих объектах-компаньонах, как это было сделано для Numeric
и OtherTypeX и т. Д. Итак, я чувствую, что я где-то ошибся.
Есть ли способ пересмотреть мой код, чтобы избежать этой избыточности И разрешить ошибку неоднозначности времени компиляции? Какова наилучшая практика в этом сценарии?
Зачем вам нужен 'fromAbelianGroup'? «AbelianGroup [T]» уже является «Monoid [T]», и компилятор предоставит вам где угодно «Monoid [T]». –
Поскольку компилятор ищет возможные преобразования в классе совместимости Monoid, поэтому без альянса Group он не найдет преобразование fromNumberic и т. Д.Кроме того, если я импортирую сопутствующий объект AbelianGroup на каждый сайт вызова, чтобы он нашел конверсии (что-то, на чем я действительно не считаю удовлетворительным решением), двусмысленность будет по-прежнему существовать даже без AbelianGroup. – Nimrand
Можете ли вы опубликовать текст с кодом, который не компилируется (v1)? – Edmondo1984