Reputation: 3756
I'm trying to write a value class that wraps Scala collection's Map
and provides an alternative get
. I'm trying to use a phantom type in the value class and tag the Key
with the same type using the method member
. If the result of member
is Some(k)
then the user should be able to call get(k)
and get a V
instead of an Option[V]
.
import scala.collection.{Map => M}
class Key[PH, K] private (val k: K) extends AnyVal
object Key {
def apply[PH, K](k: K): Key[PH, K] = new Key(k)
}
class Map[PH, K, V] private (val m: M[K, V]) extends AnyVal {
def member(k: K): Option[Key[PH, K]] = m.get(k).map(_ => Key(k))
def get(key: Key[PH, K]): V = m.get(key.k).get
}
object Map {
def apply[PH, K, V](m: M[K, V]): Map[PH, K, V] = new Map(m)
}
def withMap[K, V, T](m: M[K, V])(cont: (Map[PH, K, V] forSome { type PH }) => T): T = cont(Map(m))
withMap(M("a" -> "a")){ m =>
m.member("a") match {
case Some(v) => println(m.get(v))
case None => println(":(")
}
}
But currently it fails compiling with the following error:
found : Key[PH(in value $anonfun),String] where type +PH(in value $anonfun)
required: Key[PH(in value cont),String]
case Some(v) => println(m.get(v))
How can I convince scalac that the PH
s are the same?
Upvotes: 2
Views: 169
Reputation: 29148
Phantom types are meaningless. You should say what you mean: every Key
belongs to a certain Map
:
import scala.collection.immutable.Map // it is not safe to use Maps in general!
class KeyedMap[K, V](val m: Map[K, V]) extends AnyVal {
import KeyedMap._
def member(k: K): Option[Key[K, V, m.type]] = m.get(k).map { _ => new Key[K, V, m.type](k) }
def fromKey(k: Key[K, V, m.type]): V = m(k.k)
}
object KeyedMap {
// vvvvvvvvvvvvvv requires this parameter to be <something>.type
class Key[K, +V, M <: Map[K, V] with Singleton] private[KeyedMap](val k: K) extends AnyVal
}
object Test {
def main(args: String*): Unit = {
val m = new KeyedMap(Map("a" -> "b"))
m.member("a") match {
case Some(v) => println(m.fromKey(v))
case None => println(":(")
}
}
}
Upvotes: 1
Reputation: 29148
Destructure the existential:
withMap(M("a" -> "a")) { case m =>
m.member("a") match {
case Some(v) => println(m.get(v))
case None => println(":(")
}
}
This abbreviates
withMap(M("a" -> "a")) { case m: Map[ph, String, String] => // name the phantom type ph, name the map m =>
m.member("a") match {
case Some(v) => println(m.get(v))
case None => println(":(")
}
}
The destructuring allows m
to be given a non-existential type in terms of a newly introduced type variable. This means that every occurrence of m
can now have the same type.
Upvotes: 2