I have some code that compares a document with another and outputs pointers to fields that have changed, with the old and new values.
Some elements of the document have a sequenceId
captured by this trait:
trait ExplicitlySequencedObject {
val sequenceId: Int
}
Elements in the document that can be compared also extend a utility trait called DiffTools
which includes the following method signatures:
trait DiffTools[T] {
def createDiff[E <: DiffTools[E]](original: Seq[E], current: Seq[E], pointerString: DocumentFieldPointer): DocumentDiff
def createDiff[E <: DiffTools[E]](original: Option[Seq[E]], current: Option[Seq[E]], pointerString: DocumentFieldPointer): DocumentDiff
}
Some elements are not explicitly sequenced, but where they are I want createDiff
to work differently from this implementation, so method signatures like the following are added to DiffTools
in addition to the above:
def createDiff[S <: DiffTools[S] with ExplicitlySequencedObject](
original: Seq[S],
current: Seq[S],
pointerString: DocumentFieldPointer
): DocumentDiff
def createDiff[S <: DiffTools[S] with ExplicitlySequencedObject](
original: Option[Seq[S]],
current: Option[Seq[S]],
pointerString: DocumentFieldPointer
): DocumentDiff
When I do this I get a compiler error method createDiff is defined twice
and I'm guessing this is related to type erasure? I'd like to know why exactly this doesn't work.
Anyway I've solved the problem for now by just renaming the method (KISS, right?) but I'm thinking this is not the best scala has to offer in solving this with its fancy type system, so other approaches would be appreciated.
For example you can add one more type parameter
trait DiffTools[T, Upper] {
def createDiff[E <: DiffTools[E, Upper] with Upper](original: Seq[E], current: Seq[E], pointerString: DocumentFieldPointer): DocumentDiff
def createDiff[E <: DiffTools[E, Upper] with Upper](original: Option[Seq[E]], current: Option[Seq[E]], pointerString: DocumentFieldPointer): DocumentDiff
}
and have implementations of two sorts
class DiffToolsImpl[T] extends DiffTools[T, Any] {
override def createDiff[E <: DiffTools[E, Any]](original: Seq[E], current: Seq[E], pointerString: DocumentFieldPointer): DocumentDiff = ???
override def createDiff[E <: DiffTools[E, Any]](original: Option[Seq[E]], current: Option[Seq[E]], pointerString: DocumentFieldPointer): DocumentDiff = ???
}
class ExplicitlySequencedDiffToolsImpl[T] extends DiffTools[T, ExplicitlySequencedObject] {
override def createDiff[S <: DiffTools[S, ExplicitlySequencedObject] with ExplicitlySequencedObject](
original: Seq[S],
current: Seq[S],
pointerString: DocumentFieldPointer
): DocumentDiff = ???
override def createDiff[S <: DiffTools[S, ExplicitlySequencedObject] with ExplicitlySequencedObject](
original: Option[Seq[S]],
current: Option[Seq[S]],
pointerString: DocumentFieldPointer
): DocumentDiff = ???
}
Also you could comsider to replace F-bounds and overloading (one flavor of ad hoc polymorhism) with type classes (another flavor of ad hoc polymorphism)
http://tpolecat.github.io/2015/04/29/f-bounds.html
Something like
trait DiffTools[A] {
def createDiff(original: A, current: A, pointerString: DocumentFieldPointer): DocumentDiff
}
trait LowPriorityDiffTools {
implicit def seq[E]: DiffTools[Seq[E]] = new DiffTools[Seq[E]] {
override def createDiff(original: Seq[E], current: Seq[E], pointerString: DocumentFieldPointer): DocumentDiff = ???
}
implicit def optSeq[E]: DiffTools[Option[Seq[E]]] = new DiffTools[Option[Seq[E]]] {
override def createDiff(original: Option[Seq[E]], current: Option[Seq[E]], pointerString: DocumentFieldPointer): DocumentDiff = ???
}
}
object DiffTools extends LowPriorityDiffTools {
implicit def explicitSeq[E <: ExplicitlySequencedObject]: DiffTools[Seq[E]] = new DiffTools[Seq[E]] {
override def createDiff(original: Seq[E], current: Seq[E], pointerString: DocumentFieldPointer): DocumentDiff = ???
}
implicit def explicitOptSeq[E <: ExplicitlySequencedObject]: DiffTools[Option[Seq[E]]] = new DiffTools[Option[Seq[E]]] {
override def createDiff(original: Option[Seq[E]], current: Option[Seq[E]], pointerString: DocumentFieldPointer): DocumentDiff = ???
}
def createDiff[A](original: A, current: A, pointerString: DocumentFieldPointer)(implicit diffTools: DiffTools[A]): DocumentDiff =
diffTools.createDiff(original, current, pointerString)
}
class ExplicitlySequencedObjectImpl extends ExplicitlySequencedObject {
override val sequenceId: Int = 100
}
val opt: Option[Seq[ExplicitlySequencedObjectImpl]] = Some(Seq(new ExplicitlySequencedObjectImpl()))
DiffTools.createDiff(opt, opt, new DocumentFieldPointer {})
Type classes would be the most flexible approach.
The minimal change to your original code would be DummyImplicit
, @LuisMiguelMejíaSuárez is correct
trait DiffTools[T] {
def createDiff[E <: DiffTools[E]](original: Seq[E], current: Seq[E], pointerString: DocumentFieldPointer): DocumentDiff
def createDiff[E <: DiffTools[E]](original: Option[Seq[E]], current: Option[Seq[E]], pointerString: DocumentFieldPointer): DocumentDiff
def createDiff[S <: DiffTools[S] with ExplicitlySequencedObject](
original: Seq[S],
current: Seq[S],
pointerString: DocumentFieldPointer
)(implicit dummyImplicit: DummyImplicit): DocumentDiff
def createDiff[S <: DiffTools[S] with ExplicitlySequencedObject](
original: Option[Seq[S]],
current: Option[Seq[S]],
pointerString: DocumentFieldPointer
)(implicit dummyImplicit: DummyImplicit): DocumentDiff
}