简体   繁体   中英

scala type error for class with map member typed with existential type

I have a type system problem with scala that I can't understand. The problem is illustrated with the snippet below. What is the correct approach to be able to have a map member in a class that can have keys of type A or subtypes of A? I think the second approach is correct but I get errors when I try to get elements from the map. Has this something to do with existential types?

sealed abstract class MyBaseType
case class Concrete1() extends MyBaseType
case class Concrete2() extends MyBaseType

case class DictVariant1(data: Map[MyBaseType, Double])
case class DictVariant2(data: Map[_ <: MyBaseType, Double])

object App {
  def main(args: Array[String]) {
    val d = List((Concrete1(), 3.5)).toMap

    /* this fails with:
     * type mismatch;  
     * found: scala.collection.immutable.Map[Concrete1,Double]  
     * required: Map[MyBaseType,Double] 
     * Note: Concrete1 <: MyBaseType, but trait Map is invariant in type A. 
     * You may wish to investigate a wildcard type such as `_ <: MyBaseType`. 
     * (SLS 3.2.10)
     * 
     */
    val dv1 = DictVariant1(d)
    dv1.data.get(d)

    /* Works fine */
    val dv2 = DictVariant2(d)

    /* this fails with:
     * type mismatch;  
     * found: d.type (with underlying type scala.collection.immutable.Map[Concrete1,Double])  
     * required: _$1    
     * 
     */
    dv2.data.get(Concrete1())
  }
}

Unless you explicitly require getting the keys back out as the statically correct subtype (which seems unlikely), then the following will work:

case class DictVariant1(data: Map[MyBaseType, Double])
val d = List((Concrete1() : MyBaseType, 3.5)).toMap
val dv1 = DictVariant1(d)

Using a type ascription forces Concrete1() to be seen as an instance of the base type; you'd get the same effect if you added an explicit type to d :

val d : Map[MyBaseType, Double] = ....

Or if you put multiple entries in such that the type inferencer picks the base type:

val d = List((Concrete1(), 3.5), (Concrete2(), 4.5)).toMap

The 'get' you use is always going to fail, however, because you're trying to use the whole Map as an index: it works fine if you provide a sensible key:

dv1.data.get(Concrete1())

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM