trait Tuple
case class TCons[+H, +T <: Tuple](h: H, t: T) extends Tuple
final case object TNil extends Tuple
// Type level natural numbers -------------------------------------------------
sealed trait Nat
sealed trait Succ[P <: Nat] extends Nat
sealed trait Zero extends Nat
// Accessor type class to compute the N'th element of an Tuple L --------------
trait At[L <: Tuple, N <: Nat, Out] {
def apply(l: L): Out
}
object At {
implicit def caseZero[H, T <: Tuple]: At[H TCons T, Zero, H] =
new At[H TCons T, Zero, H] {
def apply(l: H TCons T): H = {
val (h TCons _) = l
h
}
}
implicit def caseN[H, T <: Tuple, N <: Nat, O]
(implicit a: At[T, N, O]): At[H TCons T, Succ[N], O] =
new At[H TCons T, Succ[N], O] {
def apply(l: H TCons T): O = {
val (_ TCons t) = l
a(t)
}
}
}
// An HMap is an Tuple with HEntry elements. We are reusing Tuple for it's nice syntax
final case class HEntry[K, V](value: V)
// Accessor type class to compute the element of type K in a HMap L -----------
trait PhantomGet[K, M <: Tuple, I <: Nat] // extends PhantomAny
object PhantomGet {
implicit def getHead[K, V, T <: Tuple]
: PhantomGet[K, HEntry[K, V] TCons T, Zero] = null
implicit def getTail[K, H, T <: Tuple, I <: Nat]
(implicit t: PhantomGet[K, T, I])
: PhantomGet[K, H TCons T, Succ[I]] = null
}
// Syntax ---------------------------------------------------------------------
object syntax {
object hmap {
implicit class HmapGet[M <: Tuple](m: M) {
def get[K, V, I <: Nat](k: K)
(implicit
g: PhantomGet[k.type, M, I],
a: At[M, I, HEntry[k.type, V]]
): V = a(m).value
}
def --[K, V](key: K, value: V) = HEntry[key.type, V](value)
}
}
object Test {
def main(args: Array[String]): Unit = {
import syntax.hmap._
val map1 =
TCons(HEntry[K = "name"]("foo"),
TCons(HEntry[K = "genre"](true),
TCons(HEntry[K = "moneyz"](123),
TCons(HEntry[K = "cat"]("bar"),
(TNil: TNil.type)))))
assert(map1.get("name") == "foo")
assert(map1.get("genre") == true)
assert(map1.get("moneyz") == 123)
assert(map1.get("cat") == "bar")
val map2 =
TCons(--("name" , "foo"),
TCons(--("genre" , true),
TCons(--("moneyz", 123),
TCons(--("cat" , "bar"),
TNil))))
assert(map2.get("name") == "foo")
assert(map2.get("genre") == true)
assert(map2.get("moneyz") == 123)
assert(map2.get("cat") == "bar")
}
}