Reputation: 543
I'm trying to achieve the following in scala and this seems to be beyond my "generic" skills:
I have 2 generic classes :
class A[T]
class B[T]
and I want to map some As to some Bs:
val m = Map[A, B]
Now that doesn't compile because A and B are generic, so
val m = Map[A[_], B[_]]
I want to be able to store A/B pairs for arbitrary types T. However I only want to add pairs for which the generic type is the same for the key and the value. So I can do
m updated(new A[String], new B[String])
but not
m updated(new A[String], new B[Int])
And I want the compiler to be aware of this so I can do
val a = new A[String]
val b = new A[String]
val m = Map(a -> b)
val b: B[String] = m(a) //
I was thinking a library like shapeless could help ?
Upvotes: 4
Views: 2140
Reputation: 15086
I think your best bet is to write a wrapper around Scala's Map
.
My very minimal implementation:
class MyMap[K[_],+V[_]] private(map: Map[Any,Any]) {
def apply[T](key: K[T]): V[T] = map(key).asInstanceOf[V[T]]
def updated[T1,T2,V1[X] >: V[X]](key: K[T1], value: V1[T2])(implicit ev: T1 =:= T2) = new MyMap[K,V1](map.updated(key,value))
}
object MyMap {
def apply[K[_],V[_]] = new MyMap[K,V](Map.empty)
}
I am using a cast internally, but that should be pretty safe since you make sure that the key value pairs that go into MyMap
always have the same type arguments.
scala> val (ai,as,bi,bs) = (new A[Int], new A[String], new B[Int], new B[String])
ai: A[Int] = A@51084ab3
as: A[String] = A@24b77bb0
bi: B[Int] = B@5b109ef8
bs: B[String] = B@51390faa
scala> var m = MyMap[A,B]
m: MyMap[A,B] = MyMap@666ecbca
scala> m = m.updated(as,bs)
m: MyMap[A,B] = MyMap@23ebc8c8
scala> m = m.updated(ai,bi)
m: MyMap[A,B] = MyMap@1e3e5527
scala> m(as)
res0: B[String] = B@51390faa
scala> m(ai)
res1: B[Int] = B@5b109ef8
No mixing:
scala> m = m.updated(ai,bs)
<console>:23: error: Cannot prove that Int =:= String.
m = m.updated(ai,bs)
^
Upvotes: 1
Reputation: 51271
I think this enforces the restrictions you're after.
class A[T]
class B[T]
val as = new A[String]
val bs = new B[String]
val ai = new A[Int]
val bi = new B[Int]
val ms: Map[A[X], B[X]] forSome {type X}= Map(as -> bs) // OK
val mi: Map[A[X], B[X]] forSome {type X}= Map(ai -> bi) // OK
No other combinations of as
, bs
, ai
, and bi
will compile.
Upvotes: 6