Skip to content

Instantly share code, notes, and snippets.

@akshaal
Created August 10, 2012 11:50
Show Gist options
  • Save akshaal/3313750 to your computer and use it in GitHub Desktop.
Save akshaal/3313750 to your computer and use it in GitHub Desktop.
TypedKeyMap for scala
// ---------------------------------------------------------------------------------------------
// Here is the definition of TypedKeyMap
import scala.language.implicitConversions
import scala.language.higherKinds
import scala.language.existentials
import scala.collection.immutable.{ MapLike, HashMap }
trait TypedKey {
type Value
type Self = this.type
def -> (v : Value) = (this : Self, v)
}
class TypedKeyMap[K <: TypedKey] private (underlying : HashMap[K, K#Value])
extends Iterable[TypedKeyMap.AnyTypedKeyValue[K]] {
final type AnyTypedKeyValue = TypedKeyMap.AnyTypedKeyValue[K]
def empty : TypedKeyMap[K] = TypedKeyMap.empty[K]
def get[K1 <: K](key : K1) : Option[K1#Value] = underlying.get(key).asInstanceOf[Option[K1#Value]]
def iterator : Iterator[AnyTypedKeyValue] = underlying.iterator
def -(key : K) : TypedKeyMap[K] = new TypedKeyMap(underlying - key)
def apply[K1 <: K](key : K1) : Option[K1#Value] = this.get (key)
def ++ (kvs : Seq [AnyTypedKeyValue]) : TypedKeyMap[K] = new TypedKeyMap(underlying ++ kvs)
def updated (kvs : AnyTypedKeyValue*) : TypedKeyMap[K] = this ++ kvs
def updated [K1 <: K, V1 <: K1#Value](k : K1, v : V1) : TypedKeyMap[K] = updated ((k, v))
override def size = underlying.size
}
object TypedKeyMap {
type AnyTypedKeyValue [K <: TypedKey] = (K, K#Value) forSome { type X <: K }
def apply [K <: TypedKey] (kvs : AnyTypedKeyValue[K]*) : TypedKeyMap[K] = empty[K] ++ kvs
def empty[K <: TypedKey] = new TypedKeyMap(HashMap.empty[K, K#Value])
}
// ---------------------------------------------------------------------------------------------
// Here is the definition specification of TypedKeyMap
import scala.language.existentials
import scala.reflect.runtime.universe.TypeTag
import org.specs2._
class ExistentialSpec extends Specification with matcher.ScalaCheckMatchers {
def typeTagOf [Value] (value : Value)(implicit typeTag: TypeTag[Value]) = typeTag.tpe
def typeTagOf [Value] (implicit typeTag: TypeTag[Value]) = typeTag.tpe
implicit class InferingMatcher [Value] (value : Value) (implicit valueTypeTag: TypeTag[Value]) {
def inferredAs [Type] (implicit typeTypeTag: TypeTag[Type]) = {
val expectedType = typeTagOf [Type]
val gotType = typeTagOf (value)
if (gotType <:< expectedType) {
success
} else {
execute.Failure (s"Expected $expectedType, but got $gotType")
}
}
def inferredNotAs [Type] (implicit typeTypeTag: TypeTag[Type]) = {
val expectedType = typeTagOf [Type]
val gotType = typeTagOf (value)
if (gotType <:< expectedType) {
execute.Failure (s"Not expected $expectedType, but got $gotType")
} else {
success
}
}
}
object ID extends TypedKey { type Value = Int }
object NAME extends TypedKey { type Value = String }
object WEIGHT extends TypedKey { type Value = Int }
sealed abstract class X extends TypedKey
object X1 extends X { type Value = Int }
object X2 extends X { type Value = String }
def is =
"This is a specification for TypedKeyMap. It is expected that TypedKeyMap" ^
"behaves like a map" ! {
check ((i:Int, s:String) => {
val m = TypedKeyMap [TypedKey] (ID -> i, NAME -> s)
(m get ID must_== Some (i)) and
(m get NAME must_== Some (s)) and
(m get WEIGHT must_== None)
})
} ^
"is well typed during compilation" ! {
val m = TypedKeyMap [TypedKey] ()
val x = TypedKeyMap [X] (X1 -> 1) // ID -> 1 will not compile
(m get ID).inferredAs [Option[Int]] and
(m (ID)).inferredNotAs [Option[String]] and
(m get NAME).inferredAs [Option[String]] and
(m get NAME).inferredNotAs [Option[Int]] and
(m get WEIGHT).inferredAs [Option[Int]] and
(m get WEIGHT).inferredNotAs [Nothing] and
(x get X1).inferredAs [Option[Int]] and
(x get X1).inferredNotAs [Nothing]
} ^
"implements Iterable interface" ! {
val m = TypedKeyMap.empty[TypedKey] updated (ID -> 123) updated (NAME -> "Hi") updated (WEIGHT -> 1)
def mapFun (x : m.AnyTypedKeyValue) : String =
x match {
case (ID, v : Int) => (v + 10).toString
case (NAME, v : String) => v + "x"
case (WEIGHT, v) => v.toString
}
((m map mapFun).toSet must_== Set ("133", "Hix", "1")) and
(m.size must_== 3)
}
}
// LocalWords: TypedKeyMap Iterable Hix
// ---------------------------------------------------------------------------------------------
// Test output
/*
[info] ExistentialSpec
[info]
[info] This is a specification for TypedKeyMap. It is expected that TypedKeyMap
[info] + behaves like a map
[info] + is well typed during compilation
[info] + implements Iterable interface
[info]
[info] Total for specification ExistentialSpec
[info] Finished in 361 ms
[info] 3 examples, 109 expectations, 0 failure, 0 error
[info]
[info] Passed: : Total 3, Failed 0, Errors 0, Passed 3, Skipped 0
*/)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment