Scala map containing shuffle type values

I have a function that returns (groovy code)

[words: "one two", row: 23, col: 45] 

In scala, I go to scala Map, but then I have to declare it as

 Map[String, Any] 

but this has the disadvantage that if I get access to a key, for example a map ("words") I have to add a template

 map2("words").asInstanceOf[String] 

Is there a better way in scala that doesn't require me to add asInstanceOf?

+4
source share
4 answers

To avoid casting and capitalize on static input, you can either return a tuple (String, Int, Int) :

 def getResult = ("one two", 23, 45) val res = getResult res._1 // the line // alternatively use the extractor val (line, row, _) = getResult // col is discarded line // the line row // the row 

or use the case class for the result:

 case class MyResult(line: String, row: Int, col: Int) def getResult = MyResult("one two", 23, 45) val res = getResult res.line // the line // alternatively use the extractor provided by the case class val MyResult(line, row, _) = getResult // col is discarded line // the line row // the row 

I would prefer the case class, because the fields have been named, and this is really only one line.

+10
source

This is where the class class is your friend. If your card keys are case classes, you can force client code to handle types correctly (and make it handle all types correctly).

  S:\>scala Welcome to Scala version 2.9.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> sealed abstract class Thing; defined class Thing scala> case class Toaster(slices: Int) extends Thing; defined class Toaster scala> case class Bucket(contents: String) extends Thing; defined class Bucket scala> val things = Map("toasty" -> Toaster(2), "buck" -> Bucket("stuff")); things: scala.collection.immutable.Map[java.lang.String,Product with Serializable with Thing] = Map(toasty -> Toaster(2), buck -> Bucket(stu ff)) scala> for (x <- things) x match { case (k,Toaster(s)) => println(k + " " + s) case (k,Bucket(c)) => println(k + " " + c) } toasty 2 buck stuff 

The key point here is that the match operator eliminates various cases and provides you with correctly typed variables to match the fields inside. By announcing that the abstract class is sealed, you make it clear to the compiler that it has all the available subclasses. With this information, he can tell you when you have no cases, and he can also do further optimization.

+6
source

Edit: I realized that you have this card, and I need a way to more cleanly interact with it. Of course, the case class approach, if applicable, is superior to what I suggest below.


If your keys are always mapped to the same value types, you can do something like this:

 class TypedKey[T] { def name = { // assumes you declare only `object` instances val simpleName = getClass.getSimpleName val moduleName = if (simpleName.endsWith("$")) simpleName.substring(0, simpleName.size - 1) else simpleName val lastDollar = moduleName.lastIndexOf('$') if (lastDollar == -1) moduleName else moduleName.substring(lastDollar + 1) } } object RubyKeys { object words extends TypedKey[String] object row extends TypedKey[Int] object col extends TypedKey[Int] } class MapWrapper(val underlying: Map[String, Any]) { def apply[T](key: TypedKey[T]) = underlying(key.name).asInstanceOf[T] } def main(args: Array[String]) { val map = Map("words" -> "one two", "row" -> 23, "col" -> 45) val wrapper = new MapWrapper(map) import RubyKeys._ val w = wrapper(words) // String val r = wrapper(row) // Int val c = wrapper(col) // Int println(w, r, c) } 
0
source

Having only two possible value types, as in your example, will allow you to use the Either type with the Left and Right subtypes:

 val m = Map("words" -> Left("one two"), "rows"-> Right(23), "cols"-> Right(45)) 

If you return the value from the map, you can check what you have, for example. with pattern matching or using isLeft and isRight , and expand it accordingly.

0
source

All Articles