The following statement compiles fine and works as expected:
val map : Map[_ >: Int with String, Int] = Map(1 -> 2, "Hello" -> 3)
However, if I try to add to the map:
map + ((3,4))
or
map + (("Bye", 4))
then I get a type mismatch:
found : java.lang.String("Bye")
required : _$1 where type _$1 >: Int with String
If I weaken the type signature to allow Any as the type of the Key, then this all works as expected.
My intuition says that this is to do with the nonvariance of Map’s key type, and that _$1 is somehow being fixed as a particular supertype of Int with String, but I’m not particularly happy with this. Can anybody explain what’s going on?
Edited to add:
In case you’re wondering where this arises, it’s the signature that you get if you do something like:
val map = if (true) Map(1 -> 2) else Map("1" -> 2)
You misunderstand
Int with String. It is not the Union of Int and String, it is the intersection, and for Int and String it is empty. Not the sets of values that are Int with the set of values that are strings, but the set of values that have the characteristics of Int with the characteristics of String too. There are no such values.You can use
Either[Int, String], and haveMap[Left(1) -> 2, Right("Hello") -> 3). Either is not exactly the Union, it is the discriminated union, A + B, rather than A U B. You may grasp the difference in that Either[Int, Int] is not the same thing as Int. It is in fact isomorphic to (Int, Boolean): you have an Int, and you know ont which side it is too. When A and B are disjoints (as Int and String are) A + B and A U B are isomorphic.Or (not for the faint of heart) you can have a look at a possible encoding of union types by Miles Sabin. (I’m not sure you could actually use that with preexisting class Map, and even less sure than you should even try, but it makes a most interesing reading nevertheless).
Edit: Read your question and code way too fast, sorry
Your lower bound
Int with Stringis the same asNothing, soMap[_ >: Int with String, Int], is the same asMap[_ >: Nothing, Int]and as theNothinglower bound is implied, this isMap[_, Int]. Your actualMapis aMap[Any, Int]. You could have added a boolean key, it works too, despite the Int with String. AMap[Any, Int]can be typed asMap[_, Int]so your val declaration works. But your typing loses all the information about the type of the key. Not knowing what the type of the key is, you cannot add (nor retrieve) anything from the table.An UpperBound would have been no better, as then there is no possible key. Even the initial val declaration fails.
Edit 2 : regarding
if (true) Map(1 -> 2) else Map("1" -> 2)This is not the same thing as
Map(1 -> 2, "1" -> 2). That was simpler, simply aMap[Any, Int], asAnyis the greater common supertype ofIntandString.On the other hand,
Map(1 -> 2)is aMap[Int, Int], andMap["1", 2]aMap[String, Int]. There is the problem of finding a common supertype forMap[Int, Int]andMap[String, Int], which is not finding a common supertype ofIntandString.Let’s experiment.
Mapis covariant in its second parameter. If you useIntandStringas values rather than keys :With covariance, it simply takes the common supertype of all type parameters.
With a contravariant type :
it takes the intersection
A with C.WhenBis a subtype ofA,AwithBis justB.Now with the non-variant parameter of Map when the two types are related
Such a type is not useless. You may access or add values with keys of type
B. But you cannot access values of keysA. As this is what you have in the actual Map (because of thetrue), tough luck. If you access thekeySet, it will be typedSet[A]. You have incomplete information on the type of the keys, and what you can do is limited, but this is a necessary limitation, given the limited knowledge you have on the type of the map. WithInt and String, you have minimal information, with a lower boundAnyand an upper bound equivalent toNothing. The Nothing upper bound makes it impossible to call a routine which takes a key as a parameter. You can still retrieve thekeySet, with typeSet[Any],Anybeing the lower bound.