Tutorial :Extending a Scala collection



Question:

I want a Map that throws on attempt to overwrite a value for existing key. I tried:

trait Unoverwriteable[A, B] extends scala.collection.Map[A, B] {      case class KeyAlreadyExistsException(e: String) extends Exception(e)        abstract override def + [B1 >: B] (kv: (A, B1)): Unoverwriteable[A, B1] = {          if (this contains(kv _1)) throw new KeyAlreadyExistsException(              "key already exists in WritableOnce map: %s".format((kv _1) toString)          )          super.+(kv)      }        abstract override def get(key: A): Option[B] = super.get(key)      abstract override def iterator: Iterator[(A, B)] = super.iterator      abstract override def -(key: A): Unoverwriteable[A, B] = super.-(key)  }  

and got:

<console>:11: error: type mismatch;   found   : scala.collection.Map[A,B1]   required: Unoverwirteable[A,B1]                 super.+(kv)                        ^  <console>:16: error: type mismatch;   found   : scala.collection.Map[A,B]   required: Unoverwirteable[A,B]             abstract override def -(key: A): Unoverwirteable[A, B] = super.-(key)                                                                             ^  

I'm quite new to Scala and can't figure out a way to overcome this. Any help? :)

edit: I'm using Scala 2.8.0.Beta1-prerelease (which brings some changes to scala.collection)


Solution:1

This fixed your compile error:

trait Unoverwriteable[A, B] extends scala.collection.Map[A, B] {      case class KeyAlreadyExistsException(e: String) extends Exception(e)        abstract override def + [B1 >: B] (kv: (A, B1)): scala.collection.Map[A, B1] = {          if (this contains(kv _1)) throw new KeyAlreadyExistsException(              "key already exists in WritableOnce map: %s".format((kv _1) toString)          )          super.+[B1](kv)      }        abstract override def get(key: A): Option[B] = super.get(key)      abstract override def iterator: Iterator[(A, B)] = super.iterator      abstract override def -(key: A): scala.collection.Map[A, B] = super.-(key)  }  

However, I think you really want to decorate the collection.mutable.Map#+=, as follows:

trait Unoverwriteable[A, B] extends collection.mutable.Map[A, B] {    case class KeyAlreadyExistsException(e: String) extends Exception(e)      abstract override def +=(kv: (A, B)): this.type = {      if (this contains (kv _1))        throw new KeyAlreadyExistsException("key already exists in WritableOnce map: %s".format((kv _1) toString))      super.+=(kv)    }  }  


Solution:2

As you are overriding methods in Map, you can't define your trait as the return type.

The easiest solution is to just omit the types:

abstract override def + [B1 >: B] (kv: (A, B1)) = { /* ... */ }  // ...  abstract override def -(key: A) = super.-(key)  

Or you could be explicit and add the super type:

import scala.collection.Map  abstract override def +[B1 >: B] (kv: (A, B1)): Map[A, B1] = { /* ... */ }  // ...  abstract override def -(key: A) = super.-(key): Map[A, B]  

I think you would only have to override + though, as your other methods only delegate to Map.


Solution:3

You can do it using a scala.collection.immutable.Map with a little implicit magic. That is, you define one additional method in the interface and an implicit conversion. Here's how I would do it in 2.7, I'm sure there's different methods to override in 2.8, but you should get the general idea.

trait Unoverwriteable[A, B] extends scala.collection.immutable.Map[A, B] {      import Unoverwriteable.unoverwriteableMap        case class KeyAlreadyExistsException(e: String) extends Exception(e)        def underlying: scala.collection.immutable.Map[A, B]        def update [B1 >: B] (key: A, value: B1): Unoverwriteable[A, B1] = {          if (this contains(key)) throw new KeyAlreadyExistsException(              "key already exists in WritableOnce map: %s".format(key.toString)          )          underlying update (key, value)      }        def get(key: A): Option[B] = underlying get key       def elements: Iterator[(A, B)] = underlying.elements      def -(key: A): Unoverwriteable[A,B] = underlying - key      def empty[C]: Unoverwriteable[A,C] = underlying.empty[C]      def size: Int = underlying.size  }  

Then you define the implicit in the companion object:

object Unoverwriteable {     implicit def unoverwriteableMap[A, B](map0: scala.collection.immutable.Map[A, B]): Unoverwriteable[A, B] =        new Unoverwriteable[A, B] { def underlying = map0 }    }  

To use it add an Unwriteable type annotation to your map. If you uncomment the last 2 lines in the main method, you get a KeyAlreadyExistsException as desired.

object UOMain {     def main(args: Array[String]): Unit = {        val map0 = Map((1 -> 1), (2 -> 2)): Unoverwriteable[Int, Int]        println("map0="+ map0)          val map1 = map0 - 2        println("map1="+ map1)          //val map2 = map1 + (1 -> 1000)        //println("map2" + map2)     }  }  

Note:If u also have question or solution just comment us below or mail us on toontricks1994@gmail.com
Previous
Next Post »