pferrel
pferrel

Reputation: 5702

Scala extend Map (or HashMap) and allow a constructor list of mappings

I have a special Map that will eventually do some consistency checking of values, which are restricted to have special meaning. For now I just want to create a Schema that acts exactly like a Map[String, Any], in particular I'd like to instantiate is with a list of mappings, and not force the types for the Map to be specified, so they are always [String, Any]. So instead of

val myMap:Map[String,Any] = Map("one" -> 1, "two" -> "2", ...)

I'd like to be able to have:

val mySchema:Schema = Schema("one" -> 1, "two" -> "2", ...)

Map is a trait so I think I need to extend a class like HashMap

class Schema extends HashMap[String, Any]

when I instantiate it with a list of initial mappings I get

val mySchema = new Schema("one" -> 1, "two" -> "2", ...)

Error:(110, 19) too many arguments for constructor Schema: ()drivers.Schema
  val mySchema = new Schema("one" -> 1, "two" -> "2")
              ^

There is some magic inside HashMap that is far beyond me to read (it extends 1 class with 5 traits). But it looks like the constructor's "contents" (a list of mappings?) are passed to something's initWithContents(contents) pseudo constructor. Do I need something like that there?

Upvotes: 3

Views: 5444

Answers (2)

yakshaver
yakshaver

Reputation: 2492

You'll notice that the syntax you're using to create the Schema instance is slightly different than your target syntax as well as the standard Map syntax:

Schema("one" -> 1, "two" -> "2", ...)
new Schema("one" -> 1, "two" -> "2", ...)
Map("one" -> 1, "two" -> "2", ...)

In particular, there is a new in the second case. When you create a Map without new, you are invoking a function called apply on a companion module of the same name. In this case, the function which accepts a variable length sequence of arguments is defined the Map object's inherited GenMapFactory type and it has this signature:

def apply[A, B](elems: (A, B)*): CC[A, B] = (newBuilder[A, B] ++= elems).result

The magic derives the from Scala compiler resolving this method, because methods in the companion module are part of its implicit scope. You will need to do something similar in by adding a Schema object:

object Schema {
  def apply(entries: (String, Any)*) = new Schema() ++ entries
}

If you define the object, this will work:

Schema("one" -> 1, "two" -> "2") // scala.collection.immutable.Map[String,Any](one -> 1, two -> 2)

One downside of this approach is that you lose type specificity after construction, but if you're just performing consistency checking on creation this may be enough. If you want the Schema type to be returned on invocation of the apply function or returned by other methods calls implemented in super types and enclosing scope, you will probably need to integrate with the Scala 2.8 collections API as documented here.

Upvotes: 2

class Schema(elems: Tuple2[String, Any]*) extends HashMap[String, Any] {
    this ++= elems
}

val mySchema = new Schema("one" -> 1, "two" -> "2")

Explanation:

Unfortunately, this does not work for an immutable HashMap. As far as I can tell, the only way to "extend" an immutable HashMap is by creating a class that holds an internal reference to one, as described e.g. in this answer to a similar question on SO.

Upvotes: 5

Related Questions