My guts tell me that you are probably better off not implementing a specialized FQN
collection, but hide the collection as part of the internal implementation of FQN
, effectivly removing the corresponding operations from the interface of FQN
. What I mean is, you should ask yourself whether the world must really see FQN
as a Seq
and be able use all corresponding operations, or whether it is enough to maybe add solely the operation ++
with a dispatch to the underyling list's ++
operation to FQN
's interface.
However, the Scala guys apparently did a great job in generalizing their collections and at second glance, implementing your own collection doesn't seem like a huge pain. The documentation I posted in a comment explains how to reuse code when implementing your own collection and I recommend you read it.
In short, you'd want to mix-in traits with a name of the form xxxLike
; in your specific case LinearSeqLike[String, FQN]
and override the method newBuilder: mutable.Builder[String, FQN]
to make methods like drop
return a FQN
.
The builder alone however isn't powerful enough on its own: Methods such as map
(and to my surprise ++
) may map the elements contained in the collection to a different type that is not necessary supported by your collection. In your example, FQN("foo").map(_.length) is not a legal FQN
, so the result cannot be a FQN
, however FQN("Foo").map(_.toLowercase)
is legal (at least regarding the types). This problem is solved by bringing an implicit CanBuildFrom
value to the scope.
The final implementation could look like this:
final class FQN private (underlying: List[String]) extends LinearSeq[String] with LinearSeqLike[String, FQN] {
def apply(i: Int): String = underlying.apply(i)
def length = underlying.length
/** From the documentation of {LinearSeqLike}:
* Linear sequences are defined in terms of three abstract methods, which are assumed
* to have efficient implementations. These are:
* {{{
* def isEmpty: Boolean
* def head: A
* def tail: Repr
* }}}
*/
override def isEmpty: Boolean = underlying.isEmpty
override def head: String = underlying.head
override def tail: FQN = FQN.fromSeq(underlying.tail)
override def newBuilder: mutable.Builder[String, FQN] = FQN.newBuilder
def ::(str: String) = new FQN(str :: underlying)
override def toString(): String = {
underlying.mkString(".")
}
}
object FQN {
implicit def canBuildFrom: CanBuildFrom[FQN, String, FQN] =
new CanBuildFrom[FQN, String, FQN] {
def apply(): mutable.Builder[String, FQN] = newBuilder
def apply(from: FQN): mutable.Builder[String, FQN] = newBuilder
}
def newBuilder: mutable.Builder[String, FQN] =
new ArrayBuffer mapResult fromSeq
def fromSeq(s: Seq[String]): FQN = new FQN(s.toList)
def apply(params: String*): FQN = fromSeq(params)
}