Jakob Homan
Jakob Homan

Reputation: 2294

Successfully implement MapLike in Scala

I'm having a dastard of a time implementing a Map in scala typed to (for our purposes) [String, Set[Foo]] that provides extra operations for the Foos in the values. The actual implementation is more complicated than presented below, but this is the gist. I need a class that implements all the Map-like functions and provides the extra-and-above aggregation on the collections that are the values of the map. The extends Map with MapLike patterns I've seen, particularly this, are not working.

What I've got so far:

import scala.collection.{immutable, Map, MapLike}

class Foo(a:Int) 

class CustomMap
 (val underlying:Map[String,Set[Foo]] = Map[String,Set[Foo]]())
 extends Map[String, Set[Foo]] with MapLike[String, Set[Foo], CustomMap] {
  override def empty = new CustomMap(underlying.empty)

  def -(key: String) = new CustomMap(underlying - key)

  def +[B1 >: Set[Foo]](kv: (String, B1)): scala.collection.Map[String,B1] = new CustomMap(underlying + (kv._1 -> kv._2))

  def get(key: String): Option[Set[Foo]] = underlying.get(key)

  def iterator: Iterator[(String, Set[Foo])] = underlying.iterator

  override def size = underlying.size

  def getAllFoos() = underlying.values.flatten.toSet
}

val cm1:CustomMap = new CustomMap(Map("a" -> Set(new Foo(1))))
val cm2:CustomMap = cm1 + ("a" -> Set(new Foo(2)))
println(cm2.getAllFoos)

There are issues both with the + method and accessing the extra aggregate methods.

Any pointers?

file.scala:12: error: type mismatch;
 found   : B1
 required: Set[this.Foo]
  def +[B1 >: Set[Foo]](kv: (String, B1)): scala.collection.Map[String,B1] = new CustomMap(underlying + (kv._1 -> kv._2))
                                                                                                                     ^
file.scala:24: error: type mismatch;
 found   : scala.collection.Map[String,Set[this.Foo]]
 required: this.CustomMap
val cm2:CustomMap = cm1 + ("a" -> Set(new Foo(2)))
                        ^
two errors found

Upvotes: 3

Views: 945

Answers (1)

wingedsubmariner
wingedsubmariner

Reputation: 13667

+ can't return a CustomMap because sometimes B1 won't be a Set[Foo] but some other supertype of Set[Foo]. This is the source of your errors. Map and MapLike are meant for classes that provide a map implementation that could safely have any value added to it and will return a usable Map. So a Map[String, Set[Foo]] can always have an ("", 5) put into it and become a Map[String, Any].

You can eliminate the wrapper around your underlying, as well as avoid these problems, by using the "pimp-my-library" pattern:

implicit class FooSetMap(val map: Map[String,Set[Foo]]) extends AnyVal {
  def getAllFoos = map.values.flatten.toSet
}

If you are willing to use a mutable map, take a look at collection.mutable.MultiMap. It is a mixin trait which adds extra methods to subtypes of mutable.Map[A, mutable.Set[B]] for working with multimaps - you could do something similar for your needs.

Upvotes: 3

Related Questions