Slide 1

Slide 1 text

Shapeless Introduction @pishen String :: Int :: Long :: HNil FieldType[K, H] :: T Witness.Aux[K] K <: Symbol, T <: HList, H: Type

Slide 2

Slide 2 text

Part 1 CSV writer

Slide 3

Slide 3 text

case class A(a1: String, a2: String, a3: String) case class B(b1: String, b2: String, b3: String) A("ScalaTaiwan", "is", "awesome") "ScalaTaiwan,is,awesome"

Slide 4

Slide 4 text

case class A(a1: String, a2: String, a3: String) case class B(b1: String, b2: String, b3: String) def listToCsv(list: List[String]): String

Slide 5

Slide 5 text

A B List[String] String listToCsv aToList def aToList(a: A) = List(a.a1, a.a2, a.a3) 你不覺得無聊嗎? bToList def bToList(b: B) = List(b.b1, b.b2, b.b3)

Slide 6

Slide 6 text

def classToList[T](target: T): List[String] List[String] String listToCsv A B aToList bToList

Slide 7

Slide 7 text

def classToList[T](target: T): List[String] classToList[A] classToList[B] List[String] String listToCsv A B aToList bToList

Slide 8

Slide 8 text

def classToList[T](target: T): List[String]

Slide 9

Slide 9 text

def classToHList[T](target: T): HList def classToList[T](target: T): List[String]

Slide 10

Slide 10 text

def classToList[T](target: T): List[String] import shapeless._ val h: HList = Generic[T].to(target: T)

Slide 11

Slide 11 text

HList heterogeneous list ::[H, T <: HList] HNil

Slide 12

Slide 12 text

::[String, ]

Slide 13

Slide 13 text

::[String,::[ , ]]

Slide 14

Slide 14 text

::[String,::[Int, ]]

Slide 15

Slide 15 text

::[String,::[Int,::[Boolean, ]]]

Slide 16

Slide 16 text

::[String,::[Int,::[Boolean,HNil]]] Map[String,Map[Int,Map[Boolean,Value]]] If it's hard to get the feeling...

Slide 17

Slide 17 text

Map[A,B] ::[String,::[Int,::[Boolean,HNil]]] String :: Int :: Boolean :: HNil A Map B

Slide 18

Slide 18 text

import shapeless._ val h = "ScalaTaiwan" :: 18 :: true :: HNil // h: String :: Int :: Boolean :: HNil val isAwesome = h.last // isAwesome: Boolean = true h(3) // :16: error: ... You requested to access an element at the position Succ[Succ[Succ[_0]]], but the HList ::[String,::[Int,::[Boolean,HNil]]] is too short.

Slide 19

Slide 19 text

h.head h.tail h.take(2) 1 +: h h :+ 1 h ++ h h.map(...) h.filter(...) h.flatMap(...) 可以當 List 操作的高級 Tuple

Slide 20

Slide 20 text

case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val h = Generic[Meetup].to(meetup) // h: String :: Int :: HNil Generic[Meetup].from(h) == meetup // true

Slide 21

Slide 21 text

List[String] String listToCsv A B aToList bToList

Slide 22

Slide 22 text

List[String] String listToCsv A B Generic[A].to Generic[B].to aToList bToList

Slide 23

Slide 23 text

List[String] String listToCsv A B Generic[A].to Generic[B].to HList aToList bToList

Slide 24

Slide 24 text

List[String] String listToCsv A B Generic[A].to Generic[B].to HList hlistToCsv We have to implement this! aToList bToList

Slide 25

Slide 25 text

def hlistToCsv(h: HList): String = ???

Slide 26

Slide 26 text

def hlistToCsv(h: HNil) def hlistToCsv(h: Boolean :: HNil) def hlistToCsv(h: Int :: Boolean :: HNil) def hlistToCsv(h: String :: Int :: Boolean :: HNil) def hlistToCsv(h: Int :: Boolean :: String :: HNil) def hlistToCsv(h: Boolean :: String :: Int :: HNil) def hlistToCsv(h: ...你耍我嗎? def hlistToCsv(h: HList): String = ???

Slide 27

Slide 27 text

def hlistToCsv[T](h: T)( implicit enc: CsvEncoder[T] ): String implicit val intEnc = new CsvEncoder[Int :: HNil] { def encode(t: Int :: HNil) = List(t.head.toString) } trait CsvEncoder[T] { def encode(t: T): List[String] } Int :: HNil = { enc.encode(h).map(...).mkString(",") }

Slide 28

Slide 28 text

def hlistToCsv(h: HNil) def hlistToCsv(h: Boolean :: HNil) def hlistToCsv(h: Int :: Boolean :: HNil) def hlistToCsv(h: String :: Int :: Boolean :: HNil) def hlistToCsv(h: Int :: Boolean :: String :: HNil) def hlistToCsv(h: Boolean :: String :: Int :: HNil)

Slide 29

Slide 29 text

implicit val e1: CsvEncoder[HNil] implicit val e2: CsvEncoder[Boolean :: HNil] implicit val e3: CsvEncoder[Int :: Boolean :: HNil] implicit val e4: CsvEncoder[String :: Int :: Boolean :: HNil] implicit val e5: CsvEncoder[Int :: Boolean :: String :: HNil] implicit val e6: CsvEncoder[Boolean :: String :: Int :: HNil] implicit val e7: ...不是跟剛剛一樣嗎! 還更長了!

Slide 30

Slide 30 text

implicit val e1: CsvEncoder[HNil] implicit val e2: CsvEncoder[Boolean :: HNil] implicit val e3: CsvEncoder[Int :: Boolean :: HNil] implicit val e4: CsvEncoder[String :: Int :: Boolean :: HNil] implicit val e5: CsvEncoder[Int :: Boolean :: String :: HNil] implicit val e6: CsvEncoder[Boolean :: String :: Int :: HNil] implicit val e7: ...不是跟剛剛一樣嗎! 還更長了!

Slide 31

Slide 31 text

implicit val e1: CsvEncoder[HNil] implicit conversion implicit val e2: CsvEncoder[Boolean] implicit val e3: CsvEncoder[Int] implicit val e4: CsvEncoder[String] implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T]

Slide 32

Slide 32 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T])

Slide 33

Slide 33 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil]

Slide 34

Slide 34 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil]

Slide 35

Slide 35 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: Int :: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil] CsvEncoder[Int :: Boolean :: HNil]

Slide 36

Slide 36 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: Int :: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil] CsvEncoder[Int :: Boolean :: HNil]

Slide 37

Slide 37 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: String :: Int :: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil] CsvEncoder[Int :: Boolean :: HNil] CsvEncoder[String :: Int :: Boolean :: HNil]

Slide 38

Slide 38 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: String :: Int :: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil] CsvEncoder[Int :: Boolean :: HNil] CsvEncoder[String :: Int :: Boolean :: HNil]

Slide 39

Slide 39 text

implicit val hnilEnc = new CsvEncoder[HNil] { def encode(h: HNil) = Nil } implicit val boolEnc = new CsvEncoder[Boolean] { def encode(b: Boolean) = List(a.toString) } implicit val intEnc = new CsvEncoder[Int] { def encode(i: Int) = List(i.toString) } implicit val strEnc = new CsvEncoder[String] { def encode(str: String) = List(str) }

Slide 40

Slide 40 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] = new CsvEncoder[H :: T] { def encode(h: H :: T): List[String] = hEnc.encode(h.head) ++ tEnc.encode(h.tail) }

Slide 41

Slide 41 text

List[String] String listToCsv A B Generic[A].to Generic[B].to HList hlistToCsv aToList bToList We have implemented this!

Slide 42

Slide 42 text

String A B Generic[A].to Generic[B].to HList hlistToCsv questions?

Slide 43

Slide 43 text

String A B Generic[A].to Generic[B].to HList hlistToCsv case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val csv = hlistToCsv(Generic[Meetup].to(meetup)) //csv: String = ScalaTaiwan,18

Slide 44

Slide 44 text

implicit def conv[H, T <: HList]( implicit hEnc: CsvEncoder[H], tEnc: CsvEncoder[T] ): CsvEncoder[H :: T] CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] val h: String :: Int :: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[Boolean :: HNil] CsvEncoder[Int :: Boolean :: HNil] CsvEncoder[String :: Int :: Boolean :: HNil]

Slide 45

Slide 45 text

CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] conv[]() val h: String :: Int :: Boolean :: HNil hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[String :: Int :: HNil]

Slide 46

Slide 46 text

CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] conv[]() val h: Meetup hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[String :: Int :: HNil] CsvEncoder[Meetup]

Slide 47

Slide 47 text

CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] conv[]() val h: Meetup hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[String :: Int :: HNil] implicit def genConv[T,R]( implicit gen: Generic.Aux[T,R], enc: CsvEncoder[R] ): CsvEncoder[T] CsvEncoder[Meetup] T Generic.Aux[Meetup, ] R String :: Int :: HNil

Slide 48

Slide 48 text

CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] conv[]() val h: Meetup hlistToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[String :: Int :: HNil] implicit def genConv[T,R]( implicit gen: Generic.Aux[T,R], enc: CsvEncoder[R] ): CsvEncoder[T] CsvEncoder[Meetup] T Generic.Aux[Meetup, ] R String :: Int :: HNil

Slide 49

Slide 49 text

CsvEncoder[HNil] CsvEncoder[Boolean] CsvEncoder[Int] CsvEncoder[String] conv[]() val h: Meetup classToCsv[T](h)(implicit enc: CsvEncoder[T]) CsvEncoder[String :: Int :: HNil] implicit def genConv[T,R]( implicit gen: Generic.Aux[T,R], enc: CsvEncoder[R] ): CsvEncoder[T] CsvEncoder[Meetup] T Generic.Aux[Meetup, ] R String :: Int :: HNil

Slide 50

Slide 50 text

String A B Generic[A].to Generic[B].to HList hlistToCsv

Slide 51

Slide 51 text

String A B Generic[A].to Generic[B].to HList hlistToCsv classToCsv case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val csv = hlistToCsv(Generic[Meetup].to(meetup)) //csv: String = ScalaTaiwan,18

Slide 52

Slide 52 text

String A B Generic[A].to Generic[B].to HList hlistToCsv classToCsv case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val csv = classToCsv(meetup) //csv: String = ScalaTaiwan,18

Slide 53

Slide 53 text

implicit def genConv[T,R]( implicit gen: Generic.Aux[T,R], enc: CsvEncoder[R] ): CsvEncoder[T] = new CsvEncoder[T] { def encode(t: T) = enc.encode(gen.to(t)) }

Slide 54

Slide 54 text

Part 2 JSON writer

Slide 55

Slide 55 text

case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val csv = classToCsv(meetup) //csv: String = ScalaTaiwan,18

Slide 56

Slide 56 text

case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val json = classToJson(meetup) //json: String = {"name":"ScalaTaiwan","num":18}

Slide 57

Slide 57 text

case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) literal type phantom type tagging val h = LabelledGeneric[Meetup].to(meetup) // h: FieldType["name", String] :: // FieldType["num", Int] :: // HNil h.last == 18 //true

Slide 58

Slide 58 text

String A B LabelledGeneric[A].to HList hlistToJson LabelledGeneric[B].to with phantom types

Slide 59

Slide 59 text

def hlistToJson[T](h: T)( implicit enc: ObjEncoder[T] ): String {"name":"ScalaTaiwan","num":18} trait ObjEncoder[T] { def encode(t: T): List[(String, String)] } List((name,"ScalaTaiwan"),(num,18)) key value = { val content = enc.encode(h).map { case (k, v) => "\"" + k + "\":" + v }.mkString(",") "{" + content + "}" }

Slide 60

Slide 60 text

trait ValueEncoder[T] { def encode(t: T): String } implicit val boolEnc: ValueEncoder[Boolean] = ... implicit val intEnc: ValueEncoder[Int] = ... implicit val strEnc = new ValueEncoder[String] { def encode(t: String) = "\"" + t + "\"" }

Slide 61

Slide 61 text

implicit def conv[K <: Symbol, H, T <: HList]( implicit witness: Witness.Aux[K], hEnc: ValueEncoder[H], tEnc: ObjEncoder[T] ): ObjEncoder[FieldType[K, H] :: T] ObjEncoder[FieldType["num",Int] :: HNil]

Slide 62

Slide 62 text

implicit def conv[K <: Symbol, H, T <: HList]( implicit witness: Witness.Aux[K], hEnc: ValueEncoder[H], tEnc: ObjEncoder[T] ): ObjEncoder[FieldType[K, H] :: T] = { new ObjEncoder[FieldType[K, H] :: T] { def encode(h: FieldType[K, H] :: T) = { (witness.value.name, hEnc.encode(h.head)) +: tEnc.encode(h.tail) } } } ObjEncoder[FieldType["num",Int] :: HNil]

Slide 63

Slide 63 text

implicit def conv[K <: Symbol, H, T <: HList]( implicit witness: Witness.Aux[K], hEnc: ValueEncoder[H], tEnc: ObjEncoder[T] ): ObjEncoder[FieldType[K, H] :: T] = { new ObjEncoder[FieldType[K, H] :: T] { def encode(h: FieldType[K, H] :: T) = { (witness.value.name, hEnc.encode(h.head)) +: tEnc.encode(h.tail) } } } "num" ObjEncoder[FieldType["num",Int] :: HNil]

Slide 64

Slide 64 text

String A B LabelledGeneric[A].to HList hlistToJson LabelledGeneric[B].to We have implemented this! questions?

Slide 65

Slide 65 text

implicit def genConv[T,R]( implicit gen: LabelledGeneric.Aux[T,R], enc: ObjEncoder[R] ): ObjEncoder[T] = new ObjEncoder[T] { def encode(t: T) = enc.encode(gen.to(t)) }

Slide 66

Slide 66 text

String A B LabelledGeneric[A].to HList hlistToJson LabelledGeneric[B].to classToJson

Slide 67

Slide 67 text

case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan", 18) val json = classToJson(meetup) //json: String = {"name":"ScalaTaiwan","num":18}

Slide 68

Slide 68 text

Part 3 CSV parser

Slide 69

Slide 69 text

String A B Generic[A].to Generic[B].to HList hlistToCsv

Slide 70

Slide 70 text

String A B Generic[A].from Generic[B].from HList csvToHList

Slide 71

Slide 71 text

trait CsvEncoder[T] { def encode(t: T): List[String] }

Slide 72

Slide 72 text

trait CsvDecoder[T] { def decode(l: List[String]): T } implicit val intDec = new CsvDecoder[Int] { def decode(l: List[String]) = l.head.toInt } implicit val boolDec = new CsvDecoder[Boolean] { def decode(l: List[String]) = l.head.toBoolean } implicit val strDec = new CsvDecoder[String] { def decode(l: List[String]) = l.head }

Slide 73

Slide 73 text

def hlistToCsv[T](h: T)( implicit enc: CsvEncoder[T] ): String = { enc.encode(h).map(...).mkString(",") }

Slide 74

Slide 74 text

def csvToHList[T](csv: String)( implicit dec: CsvDecoder[T] ): T = { dec.decode(csv.split(",").toList) } implicit def conv[H, T <: HList]( implicit hDec: CsvDecoder[H], tDec: CsvDecoder[T] ): CsvDecoder[H :: T] = new CsvDecoder[H :: T] { def decode(l: List[String]): H :: T = hDec.decode(l.head) :: tDec.decode(l.tail) }

Slide 75

Slide 75 text

String A B Generic[A].from Generic[B].from HList csvToHList We have implemented this! case class Meetup(name: String, num: Int) val csv = "ScalaTaiwan,18" val meetup = Generic[Meetup].from(csvToHList(csv)) //meetup: Meetup = Meetup(ScalaTaiwan,18)

Slide 76

Slide 76 text

implicit def genConv[T,R]( implicit gen: Generic.Aux[T,R], dec: CsvDecoder[R] ): CsvDecoder[T] = new CsvDecoder[T] { def decode(l: List[String]) = gen.from(dec.decode(l)) }

Slide 77

Slide 77 text

case class Meetup(name: String, num: Int) val csv = "ScalaTaiwan,18" val meetup = Generic[Meetup].from(csvToHList(csv)) //meetup: Meetup = Meetup(ScalaTaiwan,18) String A B Generic[A].from Generic[B].from HList csvToHList csvToClass

Slide 78

Slide 78 text

case class Meetup(name: String, num: Int) val csv = "ScalaTaiwan,18" val meetup = csvToClass[Meetup](csv) //meetup: Meetup = Meetup(ScalaTaiwan,18) String A B Generic[A].from Generic[B].from HList csvToHList csvToClass

Slide 79

Slide 79 text

Part 4 JSON parser

Slide 80

Slide 80 text

String A B LabelledGeneric[A].from HList jsonToHList LabelledGeneric[B].from 其實這才是最常見的一種應用 - 各大 JSON library (play-json, json4s, circe, ...) - Config parser (pureconfig) - Database schema mapping (doobie, slick, ...) with phantom types

Slide 81

Slide 81 text

pishen / scalikejdbc-generic

Slide 82

Slide 82 text

The Type Astronaut's Guide to Shapeless Dave Gurnell

Slide 83

Slide 83 text

Thank :: you!