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() } }