2015-07-29 4 views
7

В моей библиотеке, у меня есть три класса типа:Как избежать неоднозначных цепей преобразования с несколькими отношениями типа класса?

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. Компилятор находит два способ получения типа объекта класса моноидного для такого типа:

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

  2. 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 и т. Д. Итак, я чувствую, что я где-то ошибся.

Есть ли способ пересмотреть мой код, чтобы избежать этой избыточности И разрешить ошибку неоднозначности времени компиляции? Какова наилучшая практика в этом сценарии?

+0

Зачем вам нужен 'fromAbelianGroup'? «AbelianGroup [T]» уже является «Monoid [T]», и компилятор предоставит вам где угодно «Monoid [T]». –

+0

Поскольку компилятор ищет возможные преобразования в классе совместимости Monoid, поэтому без альянса Group он не найдет преобразование fromNumberic и т. Д.Кроме того, если я импортирую сопутствующий объект AbelianGroup на каждый сайт вызова, чтобы он нашел конверсии (что-то, на чем я действительно не считаю удовлетворительным решением), двусмысленность будет по-прежнему существовать даже без AbelianGroup. – Nimrand

+0

Можете ли вы опубликовать текст с кодом, который не компилируется (v1)? – Edmondo1984

ответ