I want to use a different type of card on unknown A
So you want the Map[K,V] option with the following interface, right?
trait DependentMap[K[_],V[_]] { def add[A](key: K[A], value: V[A]): DependentMap[K,V] def get[A](key: K[A]): Option[V[A]] }
Maybe it will be difficult to say from a type signature, so let's create some dummy values ββand see if the type control element agrees with what we want it to accept and reject, what we want it to reject.
// dummy definitions just to check that the types are correct case class Foo[+A](a: A) case class Bar[+A](a: A) val myMap: DependentMap[Foo,Bar] = null myMap.add(Foo( 42), Bar( 43)) // typechecks myMap.add(Foo("foo"), Bar("bar")) // typechecks myMap.add(Foo( 42), Bar("bar")) // type mismatch val r1: Option[Bar[ Int]] = myMap.get(Foo( 42)) // typechecks val r2: Option[Bar[String]] = myMap.get(Foo("foo")) // typechecks val r3: Option[Bar[String]] = myMap.get(Foo( 42)) // type mismatch
So far so good. But look what happens when we start playing with inheritance:
val fooInt: Foo[Int] = Foo(42) val fooAny: Foo[Any] = fooInt val barStr: Bar[String] = Bar("bar") val barAny: Bar[Any] = barStr println(fooInt == fooAny) // true myMap.add(fooAny, barAny).get(fooInt) // Bar("bar")?
Since fooInt and fooAny are the same value, we would naively expect this get succeed. According to the signature of the get type, this should succeed with a value of type Bar[Int] , but where did this value come from? The value we enter is of type Bar[Any] , as well as of type Bar[String] , but, of course, not of type Bar[Int] !
Here is another unexpected case.
val fooListInt: Foo[List[Int]] = Foo(List[Int]()) val fooListStr: Foo[List[String]] = Foo(List[String]()) println(fooListInt == fooListStr) // true! myMap.add(fooListInt, Bar(List(42))).get(fooListStr) // Bar(List(42))?
This time, fooListInt and fooListStr look as if they should be different, but in fact they both have a Nil value, such as List[Nothing] , which is a subtype of both List[Int] and List[String] , therefore, if we want to reproduce the behavior of Map[K,V] on this key, get will be successful again, but this is not possible, since we gave it Bar[List[Int]] and he needed to create Bar[List[String]] .
All this means that our DependentMap should not consider keys equal, unless type A specified for add is also equal to type A specified in get . Here is an implementation that uses tags to make sure this is so.
import scala.reflect.runtime.universe._ class DependentMap[K[_],V[_]]( inner: Map[ (TypeTag[_], Any), Any ] = Map() ) { def add[A]( key: K[A], value: V[A] )( implicit tt: TypeTag[A] ): DependentMap[K,V] = { val realKey: (TypeTag[_], Any) = (tt, key) new DependentMap(inner + ((realKey, value))) } def get[A](key: K[A])(implicit tt: TypeTag[A]): Option[V[A]] = { val realKey: (TypeTag[_], Any) = (tt, key) inner.get(realKey).map(_.asInstanceOf[V[A]]) } }
And here are a few examples demonstrating that it works as expected.
scala> val myMap: DependentMap[Foo,Bar] = new DependentMap scala> myMap.add(Foo(42), Bar(43)).get(Foo(42)) res0: Option[Bar[Int]] = Some(Bar(43)) scala> myMap.add(Foo("foo"), Bar("bar")).get(Foo("foo")) res1: Option[Bar[String]] = Some(Bar(bar)) scala> myMap.add(Foo(42), Bar("bar")).get(Foo(42)) error: type mismatch; scala> myMap.add[Any](Foo(42), Bar("bar")).get(Foo(42)) res2: Option[Bar[Int]] = None scala> myMap.add[Any](Foo(42), Bar("bar")).get[Any](Foo(42)) res3: Option[Bar[Any]] = Some(Bar(bar)) scala> myMap.add(Foo(List[Int]()), Bar(List(43))).get(Foo(List[Int]())) res4: Option[Bar[List[Int]]] = Some(Bar(List(43))) scala> myMap.add(Foo(List[Int]()), Bar(List(43))).get(Foo(List[String]())) res5: Option[Bar[List[String]]] = None
It works, but an acting freak. I'd rather find a better way.
Then you are probably disappointed that my get implemented using a throw. Let me try to convince you that there is no other way.
Instead of a card that may or may not contain our key, consider a much simpler case.
def safeCast[A,B]( value: A )( implicit tt1: TypeTag[A], tt2: TypeTag[B] ): Option[B] = { if (tt1 == tt2) Some(value.asInstanceOf[B]) else None }
Given the type tag for A and the type tag for B, if the tags of the two types are equal, then we know that A and B are the same type, and therefore the type is safe. But how can we implement this without coercion? An equality test returns just a logical, not evidence of equality, for example, in some other languages . Therefore, there is nothing that statically distinguishes between the two if branches, and the compiler cannot know that conversion without restrictions is legal in the "true" branch, but illegal in the other.
In the more complex case of our DependentMap we also need to compare our type tag with the one we saved when we did add , and therefore we also need to use cast.
I'm going to answer to use existential types with the forSome
I understand why you think so. You want the association group to have a value for the value, where each pair (key, value) is of type (Foo[A], Bar[A]) forSome {type A} . Indeed, if you were not interested in performance, you can save these associations in a list:
val myList: List[(Foo[A], Bar[A]) forSome {type A}]
Since forSome is inside the brackets, this allows each entry in the list to use a different A. And since Foo[A] and Bar[A] are to the left of forSome , in each entry A must match.
In the case of the Card, however, there is no place to put forSome to get the desired result. The problem with Map is that it has two types of parameters, which prevents us from placing them to the left of forSome without putting forSome outside the brackets. This would not make sense: since the parameters of the two parameters are independent, there is nothing that connects one occurrence of a parameter of the left type with the corresponding occurrence of a parameter of the correct type, and therefore there is no way to find out which A should match. Consider the following counter example:
case class NotMap[K,V](k1: K, k2: K, v1: V, v2: V, v3: V)
There are not the same number of K values ββas there are V values, therefore, in particular, there is no correspondence between K values ββand V. If there was any special syntax, for example Map[{Foo[A], Bar[A]} forSome {type A}] , which would allow us to indicate that A must match A for each entry in the Map, then you can also use this syntax to indicate the meaningless type NotMap[{Foo[A], Bar[A]} forSome {type A}] . Therefore, there is no such syntax, and we need to use a type other than Map , such as DependentMap or HMap .