aboutsummaryrefslogtreecommitdiff
path: root/tests/run/hmap.scala
blob: b76668faa15430e39bd46f8ca415a511851ebe6e (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
trait Tuple
trait ::[H, T <: Tuple] extends Tuple
case class TupleCons[H, T <: Tuple](h: H, t: T) extends ::[H, T]
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 :: T, Zero, H] =
    new At[H :: T, Zero, H] {
      def apply(l: H :: T): H = {
        val TupleCons(h, _) = l
        h
      }
    }

  implicit def caseN[H, T <: Tuple, N <: Nat, O]
    (implicit a: At[T, N, O]): At[H :: T, Succ[N], O] =
      new At[H :: T, Succ[N], O] {
        def apply(l: H :: T): O = {
          val TupleCons(_, 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] :: T, Zero] = null

  implicit def getTail[K, H, T <: Tuple, I <: Nat]
    (implicit t: PhantomGet[K, T, I])
    : PhantomGet[K, H :: 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
    }

    implicit class EntryAssoc[K](k: K) {
      def -- [V](value: V): HEntry[K, V] = HEntry(value)
    }

    type --[A, B] = HEntry[A, B]
  }
}

object Test {
  def main(args: Array[String]): Unit = {
    type MapType1 =
      HEntry["name",   String]  ::
      HEntry["genre",  Boolean] ::
      HEntry["moneyz", Int]     ::
      HEntry["cat",    String]  ::
      TNil.type

    // Since
    val map1: MapType1 =
      TupleCons(HEntry[K = "name"]("foo"),
      TupleCons(HEntry[K = "genre"](true),
      TupleCons(HEntry[K = "moneyz"](123),
      TupleCons(HEntry[K = "cat"]("bar"),
      TNil))))

    import syntax.hmap._

    assert(map1.get("name") == "foo")
    assert(map1.get("genre") == true)
    assert(map1.get("moneyz") == 123)
    assert(map1.get("cat") == "bar")

    type MapType2 =
      "name"   -- String  ::
      "genre"  -- Boolean ::
      "moneyz" -- Int     ::
      "cat"    -- String  ::
      TNil.type

    val map2: MapType2 =
      TupleCons("name"   -- "foo",
      TupleCons("genre"  -- true,
      TupleCons("moneyz" -- 123,
      TupleCons("cat"    -- "bar",
      TNil))))

    assert(map2.get("name") == "foo")
    assert(map2.get("genre") == true)
    assert(map2.get("moneyz") == 123)
    assert(map2.get("cat") == "bar")
  }
}