here’s my take: Scastie - An interactive playground for Scala.
abstract class SeqWrapper[Elem, Wrapper <: SeqWrapper[Elem, _]](elems: Elem*) {
protected def wrap(rawCollection: Seq[Elem]): Wrapper
protected def unwrap: Seq[Elem] = elems
// below methods don't need to be overridden in subclasses
def ++(other: Wrapper): Wrapper = wrap(unwrap ++ other.unwrap)
def :+(elem: Elem): Wrapper = wrap(elems :+ elem)
}
case class Track(whatever: Int)
case class Tracks(tracks: Vector[Track]) extends SeqWrapper[Track, Tracks](tracks*) {
def this(tracks: Track*) = this(Vector(tracks*))
override protected def wrap(rawCollection: Seq[Track]): Tracks =
Tracks(rawCollection.toVector)
override protected def unwrap: Vector[Track] =
tracks
}
@main def test() = {
val tracks = new Tracks(Track(42), Track(27))
assert(tracks ++ new Tracks(Track(5)) == tracks :+ Track(5))
println(tracks :+ Track(5))
}
it would need some extra work to add a base class for companion objects to have the apply
method to avoid using new
keyword.
note that if you carefully manage the underlying sequence, so that it doesn’t change its implementation, then in methods like:
override protected def wrap(rawCollection: Seq[Track]): Tracks =
Tracks(rawCollection.toVector)
the .toVector
will be no-op, as the rawCollection
will be Vector
already. maybe there’s a way to enforce it statically (on type-level?).
also note that the solution from @som-snytt is more lightweight as it doesn’t re-wrap the underlying sequence constantly.