2

Given a Map[String, Int] in Scala, I want to create a trait that allows me to add extra logic to the add and get methods. What would be the correct syntax for doing this? (Note: I also want to invoke the super method in each case)

I tried something like:

var mymap: Map[String, Int] with mytrait[String, Int] = Map[String, Int]()

trait mytrait[A, B] {

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

}

But the interpreter complains so I'm obviously missing something syntactically, specifically with the use of the type parameters.

Many thanks for the help

To be more specific: What I have is a map already in place in my code, and so rather than rewrite large chunks of my program, I want to add logic to the + method and get method of the Map so that it does extra logic every time items are added to the map elsewhere in the program. Hence why I opted for a trait to add functionality to the map

Here is my code at present :

    trait RegManager[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] = {

        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)
}



var regs = new scala.collection.Map[String, Int] with RegManager[String, Int]

Update: In the end I opted for a wrapper style implementation (Decorator DP) which got the job done. I am only 6 months in to my Scala career so I might not have understood the capabilities of traits correctly yet?! Great language though!

Darius
  • 5,180
  • 5
  • 47
  • 62
  • please show us how you instantiate your map. – Adam Rabung Aug 07 '12 at 11:04
  • 1
    Your last paragraph sounds as if you wanted to change the behaviour of a single, already existing object. You cannot do this with a trait, but you could do it with a wrapper around the object. However, if other components hold references to the wrappee directly, than they'll bypass your wrapper. Have a look at http://stackoverflow.com/questions/1913591/understanding-why-pimp-my-library-was-defined-that-way-in-scala, and http://www.artima.com/weblogs/viewpost.jsp?thread=275135 and https://github.com/kevinwright/Autoproxy-Lite – Malte Schwerhoff Aug 07 '12 at 11:09
  • Firstly the compiler complains that + does not override anything. Try to remove the + or extend a class that has a + function. – tgr Aug 07 '12 at 11:15
  • I've added my code as it is at the moment for @Adam Rabung I realise I could just roll out a Decorator style implementation, but I just thought Scala's trait feature might be able to help me here. Is this an incorrect assumption? – Darius Aug 07 '12 at 11:24

2 Answers2

1

either MyTrait[A,B] extends Map[A,B] or MyTrait[A,B] {this: Map[A,B] => ...


Sorry, in fact it is unlikely you can make that work this way.

First part of my answer was related to the fact that your declaration of MyTrait does not reference Map, so there is no chance that it could override the + of Map. You need something like

trait MyTrait[A,B] extends Map[A,B] {
  abstract override def +[B1 >: B](kv: (A, B1)) : Map[A, B1] = {
    println("adding to map") // this is the new part
    super.+(kv) 
   }
}

However that will not get you very far, for many reasons :

in the declaration

var mymap: Map[String, Int] with mytrait[String, Int] = Map[String, Int]()

the value Map[String, Int]() has type Map and is unrelated to your trait. Declaring the variable mymap of type Map with mytrait will not make it so. It will just prevent it to compile.

MyTrait must appear in the creation of the map, not just in the type declaration of the variable. That would be something like new Map[A,B] with MyTrait[A,B] (note the new).

However, Map is a trait, not a concrete class, so you have to bring in some concrete implementation. Should you manage to do that, you then have to be careful that the new map returned by + has type MyTrait mixed in too (which of course your concrete implementation is unlikely to do). Otherwise, once you have added one element, you are back to a Map without MyTrait.

Didier Dupont
  • 29,398
  • 7
  • 71
  • 90
  • Thanks for your help. Could you add a more complete code example as I am still not totally clear on this. If you could show an instantiation of the map with a trait (that is defined to override just the add method, and call super.+ ) then I would really appreciate it and can choose your answer. Thanks – Darius Aug 07 '12 at 10:11
0

You cannot add a trait to an existing object. Trait cannot be added dynamically, they can only be added when the object is created. You can either:

  1. Use the pimp your library pattern, as suggested in the comments of your question. However, you cannot use the same + mnethod in this case, you need to use your own method name.
  2. Wrap your Map manually, so that you can still use + as a method name.
  3. Create a copy of your object and add a trait to it. It can lead to performance issues, especially if the original map is large.
Community
  • 1
  • 1
Nicolas
  • 24,509
  • 5
  • 60
  • 66
  • Thanks, but this doesn't quite completely answer what I'm after. I have edited my question to add more detail – Darius Aug 07 '12 at 10:57
  • Yes, the edit has totally changed the originl question ;) I've edited my answer. – Nicolas Aug 07 '12 at 11:35