aboutsummaryrefslogtreecommitdiff
path: root/tests/run/hmap.scala
blob: d84419ce1e8dd8660f16be6357abefc93a6b94eb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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")
  }
}