Scala: existential types for Map

I want to use a different type of card on unknown A:

val map: Map[Foo[A], Bar[A]] = ... ... val foo = new Foo[Qux] val bar: Bar[Qux] = map(foo) 

This does not work because A is unknown. I should define it instead:

 val map: Map[Foo[_], Bar[_]] = ... ... val foo = new Foo[Qux] val bar: Bar[Qux] = map(foo).asInstanceOf[Bar[Qux]] 

It works, but an acting freak. I would rather find a better way. I believe the answer is to use existential types with the forSome , but I don't understand how this works. Should it be:

 Map[Foo[A], Bar[A]] forSome { type A } 

or

 Map[Foo[A] forSome { type A }, Bar[A]] 

or

 Map[Foo[A forSome { type A }], Bar[A]] 
+4
source share
3 answers

Actually, none of these works.

 Map[Foo[A], Bar[A]] forSome { type A } 

- this is Map , where all the keys are of the same type Foo[A] and values ​​of type Bar[A] (but type A may be different for different cards of this type); In the second and third examples, A in Bar[A] completely different from A under forSome .

This ugly workaround should work:

 // need type members, so can't use tuples case class Pair[A, B](a: A, b: B) { type T1 = A type T2 = B } type PairedMap[P <: Pair[_, _]] = Map[P#T1, P#T2] type FooBarPair[A] = Pair[Foo[A], Bar[A]] val map: PairedMap[FooBarPair[_]] = ... 
+8
source

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 .

+1
source

Sort of

 def map[A]: Map[Foo[A], Bar[A]] = ... val myMap = map[Qux] ... val foo = new Foo[Qux] val bar: Bar[Qux] = myMap(foo) 

Or (to which Alexey Romanov answers)

 type MyMap[A] = Map[Foo[A],Bar[A]] val map:MyMap[Qux] = ... ... val foo = new Foo[Qux] val bar: Bar[Qux] = map(foo) 
0
source

All Articles