简体   繁体   中英

Binding Existential types in Scala

This is my basic CMap which map classes (Class[T] for any T) to any type of value.

scala> type CMap = Map[Class[T] forSome{type T}, Any]
defined type alias CMap

scala> val cMap: CMap = Map(classOf[Int]->5, classOf[String]->"abc", classOf[Double]->"ddd")
cMap: CMap = Map(int -> 5, class java.lang.String -> abc, double -> ddd)

Now I want a "bound" CMap (call it CMapBind). Like CMap, it maps classes (any classes) to values (any values). But unlike the CMap, CMapBind has a type binding between the key and value, meaning I hope the following behavior:

val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]-> "aa") // should compile
val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]-> 0)    // should fail compile

How do I implement CMapBind?

I know the following two won't work syntactically/logically.

scala> type CMapBind = Map[Class[T] forSome{type T}, T]
<console>:8: error: not found: type T
       type CMapBind = Map[Class[T] forSome{type T}, T]


scala> type CMapBind = Map[Class[T], T] forSome{type T}
scala> val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]->"str")
<console>:8: error: type mismatch;
 found   : scala.collection.immutable.Map[Class[_ >: String with Int],Any]
 required: CMapBind
    (which expands to)  Map[Class[T],T] forSome { type T }
       val cMapBind: CMapBind = Map(classOf[Int]->5, classOf[String]->"str")

Note that here I am using type constructor Class[T] as an example to illustrate the question. In my code I have my own types, eg trait Animal[S, T], class Dog extends Animal[Int, String] .

Edit 1: I should have mentioned that I am using immutable Map as an example, but what I really need is a mutable heterogeneous Map).

Let's try to implement mutable HMap . Some explanations about Scala type system is here by @MilesSabin: http://www.chuusai.com/2011/07/16/fundeps-in-scala/

The idea is to statically check the constructor (here you will see, that it's arity depends on your hands, so it is possible to generate it or smth else), and the insert method. By the way, same way immutable HMap implemented in shapeless.

import scala.collection.mutable.Map

class HMapBuilder[R[_, _]] { // constructor arity is two
  def apply[K1, V1](e1: (K1, V1))(implicit ev1: R[K1, V1]) = 
    new HMap[R](Map(e1))
  def apply[K1, V1, K2, V2](e1: (K1, V1), e2: (K2, V2))
                           (implicit ev1: R[K1, V1], ev2: R[K2, V2]) =
    new HMap[R](Map(e1, e2))
}

So it is a constructor of our map. Evidences will statically check types of the inserting data. Next, lets wrap the default scala collection:

class HMap[R[_, _]](underlying : Map[Any, Any] = Map.empty) {
  def get[K, V](k : K)(implicit ev : R[K, V]) : Option[V] = 
    underlying.get(k).asInstanceOf[Option[V]]

  def +=[K, V](kv : (K, V))(implicit ev : R[K, V]) : HMap[R] = {
    underlying += kv
    this
  }
  def -=[K](k : K) : HMap[R] = {
    underlying -= k
    this
  }

  override def toString = underlying.toString
}

Finally wrapping HMapBuilder , to make a pleasant constructor:

object HMap {
  def apply[R[_, _]] = new HMapBuilder[R]

  def empty[R[_, _]] = new HMap[R]
  def empty[R[_, _]](underlying : Map[Any, Any]) = 
    new HMap[R](underlying)
}

In result, the usage is similar to shapeless HMap :

class Mapping[K, V]

implicit def mappingFromClass[A] = new Mapping[Class[A], A]

val hm = HMap[Mapping](classOf[Int] -> 5) // ok
hm += (classOf[String] -> "string") // ok
hm += (classOf[Boolean] -> false) // ok
hm += (classOf[Double] -> "sss") // compile fail    

Works as expected. I implemented only insert and remove functions, other functions is possible to define same way.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM