Created
August 10, 2012 11:50
-
-
Save akshaal/3313750 to your computer and use it in GitHub Desktop.
TypedKeyMap for scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// --------------------------------------------------------------------------------------------- | |
// 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