Map("name" -> "foo") is a function call, not a constructor, this means you cannot write:
Map("name" -> "foo") with MoreFilterOperations
what can you write
val m = Map("name" -> "foo") val m2 = m with MoreFilterOperations
To get mixin, you must use a specific type, the naive first attempt would be something like this:
def EnhMap[K,V](entries: (K,V)*) = new collection.immutable.HashMap[K,V] with MoreFilterOptions[(K,V)] ++ entries
Using the factory method here to avoid duplication of type parameters. However, this will not work, because the ++ method will simply return a plain old HashMap without mixin!
The solution (as Sam suggested) involves using an implicit conversion to add the pimped method. This will allow you to convert the map with all the usual methods and still use your additional methods on the resulting map. I usually do this with the class instead of the attribute, since having the available constructor parameters leads to a stronger syntax:
class MoreFilterOperations[T](t: Traversable[T]) { def filterFirstTwo(f: (T) => Boolean) = t filter f take 2 } object MoreFilterOperations { implicit def traversableToFilterOps[T](t:Traversable[T]) = new MoreFilterOperations(t) }
It allows you to write
val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3") val m2 = m filterFirstTwo (_._1.startsWith("n"))
But he still doesn't work very well with the collection map. You started with a map and ended with Traversable . It's not about how things should work. The trick here is also to abstract by type of collection using higher types
import collection.TraversableLike class MoreFilterOperations[Repr <% TraversableLike[T,Repr], T] (xs: Repr) { def filterFirstTwo(f: (T) => Boolean) = xs filter f take 2 }
Simple enough. You must specify Repr , the type representing the collection, and T , the type of elements. I use TraversableLike instead of Traversable as it implements its view; without it, filterFirstTwo will return Traversable regardless of the type of start.
Now implicit conversions. This is where things get a little more complicated in type notation. First, I use a higher type to capture the collection view: CC[X] <: Traversable[X] , this parameter indicates the type CC , which should be a subclass of Traversable (note the use of X as a placeholder here, CC[_] <: Traversable[_] does not mean the same thing).
There is also an implicit CC[T] <:< TraversableLike[T,CC[T]] , which the compiler uses to statically ensure that our CC[T] collection is indeed a subclass of TraversableLike and therefore a valid argument for the MoreFilterOperations constructor:
object MoreFilterOperations { implicit def traversableToFilterOps[CC[X] <: Traversable[X], T] (xs: CC[T])(implicit witness: CC[T] <:< TraversableLike[T,CC[T]]) = new MoreFilterOperations[CC[T], T](xs) }
So far so good. But there is one more problem ... It will not work with cards, because they take two types of parameters. The solution is to add another implicit MoreFilterOperations object using the same principles as before:
implicit def mapToFilterOps[CC[KX,VX] <: Map[KX,VX], K, V] (xs: CC[K,V])(implicit witness: CC[K,V] <:< TraversableLike[(K,V),CC[K,V]]) = new MoreFilterOperations[CC[K,V],(K,V)](xs)
Real beauty comes when you also want to work with types that are not really collections, but that can be displayed as if they were. Remember the Repr <% TraversableLike in the MoreFilterOperations constructor? This view restriction also allows types that can be implicitly converted to TraversableLike , as well as direct subclasses. Lines are a classic example of this:
implicit def stringToFilterOps (xs: String)(implicit witness: String <%< TraversableLike[Char,String]) : MoreFilterOperations[String, Char] = new MoreFilterOperations[String, Char](xs)
If you run it now in REPL:
val m = Map("name"->"foo", "name2"->"foo2", "name3"->"foo3") // m: scala.collection.immutable.Map[java.lang.String,java.lang.String] = // Map((name,foo), (name2,foo2), (name3,foo3)) val m2 = m filterFirstTwo (_._1.startsWith("n")) // m2: scala.collection.immutable.Map[java.lang.String,java.lang.String] = // Map((name,foo), (name2,foo2)) "qaxfwcyebovjnbointofm" filterFirstTwo (_ < 'g') //res5: String = af
The card is coming, the card is coming out. The line goes, the line goes. etc...
I have not tried it with Stream or Set or Vector , but you can be sure that if you did, it would return the same type of collection that you started working with.