summaryrefslogtreecommitdiff
path: root/playground.scala
blob: 603ed08c18c6d1a94ee4bac6674ec1f43e9a9d38 (plain)
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import shapeless.Generic

class Playground {
  class ScalaStdlibTypes { // Motivation
    class ScalaConcreteTypesWithTypes {
      type Rect = (Int, Int) // this is a Product (AND relation)
      type Circle = Int
      type Shape = Either[Rect, Circle] // either implies a Coproduct (OR relation)

      def area(s: Shape) = s match {
        case Left((a, b)) => a*b
        case Right(a) => math.Pi*a*a
      }

      val rect: Shape = Left(4, 2)
      val circle: Shape = Right(1)
      println(area(rect), area(circle))
    }
    val concrete_types = new ScalaConcreteTypesWithTypes

    class ScalaGenericTypes {
      trait Shape
      case class Rect(a: Int, b: Int) extends Shape
      case class Circle(a: Int) extends Shape

      def area(s: Shape) = s match {
        case Rect(a, b) => a*b
        case Circle(a) => math.Pi*a*a
      }

      val rect = new Rect(3, 4)
      val circle = new Circle(3)
      println(area(rect))
    }
    val generic_types = new ScalaGenericTypes
  }

  class ShapelessGenericTypes {
    class FromTuples {
      import shapeless.{HList, ::, HNil}
      type testType = Int :: Int :: HNil
      val k: testType = (1 :: 1 :: HNil) // manual construction of a "repr" (generic representation of a type) from a tuple

      val newType = Generic[(Int, Int)]
      println(newType.to((1, 1)))
    }
    class FromCaseClasses {
      trait Shape
      case class Rect(a: Int, b: Int) extends Shape

      val rectGen = Generic[Rect]      
      println(rectGen)
    }
    val case_classes = new FromCaseClasses
  }

  // val stdlib_types = new ScalaStdlibTypes
  val shapless_generic_types = new ShapelessGenericTypes

  class GenericCSVEncoder {
    trait CSVEncoder[A] { // generic typeclass that defines the encode operation
      def encode(s: A): List[String]
    }
    def writeCSV[A](vals: List[A])(implicit enc: CSVEncoder[A]): String =
      vals.map(v => enc.encode(v).mkString(",")).mkString("\n")

    /** Simple example with a case class
      */
    case class Employee(a: String, b: Int)
    implicit val employeeEncoder: CSVEncoder[Employee] = {
      new CSVEncoder[Employee] {
        def encode(e: Employee): List[String] = List(e.a, e.b.toString)
      }
    }
    val employees = List(Employee("a", 1), Employee("b", 2))
    println(writeCSV(employees))

    /** compiler searches for (i.e. resolves )an implicit encoder function matching 
      * the given type
      * 
      * needs an implicit encoder function in the context for both the types
      */
    implicit def pairEncoder[A, B] (
      implicit
        aEncoder: CSVEncoder[A],
      bEncoder: CSVEncoder[B]
    ): CSVEncoder[(A, B)] =
      new CSVEncoder[(A, B)] {
        def encode(pair: (A, B)): List[String] = {
          val (a, b) = pair
          aEncoder.encode(a) ++ bEncoder.encode(b)
        }
      }
    implicit val icecreamEncoder: CSVEncoder[IceCream] = {
      new CSVEncoder[IceCream] {
        def encode(e: IceCream): List[String] = List(e.a, e.b.toString)
      }
    }
    case class IceCream(a: String, b: Int)
    val icecream = List(IceCream("a", 1), IceCream("b", 2))
    println(writeCSV(employees zip employees))

    // Standard patterns with companion objects

    trait CsvEncoder2[T] {
      def encode(k: T): List[String]
    }
    /** Demonsrates a standard pattern for generic typeclass companion objects
      * 
      * apply performs implicit compiler resolution, while 
      * instance wraps a HOF into the encode function defined by the trait
      */
    object CsvEncoder2 {
      // "Summoner" method
      def apply[A](implicit enc: CSVEncoder[A]): CSVEncoder[A] = enc

      // "Constructor" method
      def instance[A](func: A => List[String]): CSVEncoder[A] =
        new CSVEncoder[A] {
          def encode(value: A): List[String] =
            func(value)
        }
    }
    // with the new companion object, encode for ice cream can be:
    // implicit val icecreamEncoder2: CsvEncoder2[IceCream] =
    //   instance(x => List(x.a, x.b.toString))

  }
  val generic_csv_encoder = new GenericCSVEncoder
}

object main {
  def main(args: Array[String]): Unit = {
    new Playground()
  }
}