Scala map containing mix type values

后端 未结 4 1329
感情败类
感情败类 2020-12-19 13:21

I have a function that returns ( groovy code)

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

In scala I change above to scala Map but then I am for

相关标签:
4条回答
  • 2020-12-19 13:46

    Having only two possible value types as in your example would allow to use the Either type with the sub-types Left and Right:

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

    If you get back a value from the map, you can check what you have, e.g. with pattern matching or using isLeft and isRight, and "unwrap" it accordingly.

    0 讨论(0)
  • 2020-12-19 13:56

    Edit: I had understood that you got that map as is and needed a way to interface more cleanly with it. The case-class approach is, if applicable, superior to what I'm proposing below, of course.


    If your keys are always mapped to the same value types, you could 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 讨论(0)
  • 2020-12-19 13:58

    This is where case classes are your friend. If your map's keys are case classes, then you can force client code to correctly handle the types (and force it to correctly handle all of the types).

            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 here is that the match statement is cracking out the various cases, and providing you with properly typed variables to match the fields within. By declaring that the abstract class as sealed, you are letting the compiler know that it has all of the available subclasses. With that information it can tell you when you have missing cases, and it could also do further optimization.

    0 讨论(0)
  • 2020-12-19 14:01

    In order to avoid the casting and benefit from static-typing, 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 a 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 are named and it's really just one line more.

    0 讨论(0)
提交回复
热议问题