user1498572
user1498572

Reputation: 543

Scala Map with generic classes as Key/Value types

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

Answers (2)

Jasper-M
Jasper-M

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

jwvh
jwvh

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

Related Questions