Different types in Map Scala

后端 未结 7 1540
情书的邮戳
情书的邮戳 2020-12-14 03:11

I need a Map where I put different types of values (Double, String, Int,...) in it, key can be String.

Is there a way to do this, so that I get the correct type with

相关标签:
7条回答
  • 2020-12-14 03:28

    I don't think there's a way to get bare map.apply() to do what you'd want. As the other answers suggest, some sort of container class will be necessary. Here's an example that restricts the values to be only certain types (String, Double, Int, in this case):

    sealed trait MapVal
    case class StringMapVal(value: String) extends MapVal
    case class DoubleMapVal(value: Double) extends MapVal
    case class IntMapVal(value: Int) extends MapVal
    
    val myMap: Map[String, MapVal] =                                                               
      Map("key1" -> StringMapVal("value1"),
          "key2" -> DoubleMapVal(3.14),
          "key3" -> IntMapVal(42))
    
    myMap.keys.foreach { k =>
      val message =
        myMap(k) match { // map.apply() in your example code
          case StringMapVal(x) => "string: %s".format(x)
          case DoubleMapVal(x) => "double: %.2f".format(x)
          case IntMapVal(x) => "int: %d".format(x)
        }
      println(message)
    }
    

    The main benefit of the sealted trait is compile-time checking for non-exhaustive matches in pattern matching.

    I also like this approach because it's relatively simple by Scala standards. You can go off into the weeds for something more robust, but in my opinion you're into diminishing returns pretty quickly.

    0 讨论(0)
  • 2020-12-14 03:34

    There is a way but it's complicated. See Unboxed union types in Scala. Essentially you'll have to type the Map to some type Int |v| Double to be able to hold both Int and Double. You'll also pay a high price in compile times.

    0 讨论(0)
  • 2020-12-14 03:35

    If you want to do this you'd have to specify the type of Container to be Any, because Any is a supertype of both Double and String.

    val d: Container[Any] = new Container(4.0)
    val str: Container[Any] = new Container("string")
    val m: Map[String, Container[Any]] = Map("double" -> d, "string" -> str)
    

    Or to make things easier, you can change the definition of Container so that it's no longer type invariant:

    class Container[+T](element: T) {
      def get: T = element
      override def toString = s"Container($element)"
    }
    
    val d: Container[Double] = new Container(4.0)
    val str: Container[String] = new Container("string")
    val m: Map[String, Container[Any]] = Map("double" -> d, "string" -> str)
    
    0 讨论(0)
  • 2020-12-14 03:38

    This is now very straightforward in shapeless,

    scala> import shapeless._ ; import syntax.singleton._ ; import record._
    import shapeless._
    import syntax.singleton._
    import record._
    
    scala> val map = ("double" ->> 4.0) :: ("string" ->> "foo") :: HNil
    map: ... <complex type elided> ... = 4.0 :: foo :: HNil
    
    scala> map("double")
    res0: Double with shapeless.record.KeyTag[String("double")] = 4.0
    
    scala> map("string")
    res1: String with shapeless.record.KeyTag[String("string")] = foo
    
    scala> map("double")+1.0
    res2: Double = 5.0
    
    scala> val map2 = map.updateWith("double")(_+1.0)
    map2: ... <complex type elided> ... = 5.0 :: foo :: HNil
    
    scala> map2("double")
    res3: Double = 5.0
    

    This is with shapeless 2.0.0-SNAPSHOT as of the date of this answer.

    0 讨论(0)
  • 2020-12-14 03:38

    I finally found my own solution, which worked best in my case:

    case class Container[+T](element: T) {
        def get[T]: T = {
            element.asInstanceOf[T]
        }
    }
    
    val map: Map[String, Container[Any]] = Map("a" -> Container[Double](4.0), "b" -> Container[String]("test"))
    val double: Double = map.apply("a").get[Double]
    val string: String = map.apply("b").get[String]
    
    0 讨论(0)
  • 2020-12-14 03:38

    (a) Scala containers don't track type information for what's placed inside them, and

    (b) the return "type" for an apply/get method with a simple String parameter/key is going to be static for a given instance of the object the method is to be applied to.

    This feels very much like a design decision that needs to be rethought.

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