Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Shapeless Introduction

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

Shapeless Introduction

Avatar for Pishen Tsai

Pishen Tsai

July 12, 2017
Tweet

More Decks by Pishen Tsai

Other Decks in Programming

Transcript

  1. Shapeless Introduction @pishen String :: Int :: Long :: HNil

    FieldType[K, H] :: T Witness.Aux[K] K <: Symbol, T <: HList, H: Type
  2. 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"
  3. case class A(a1: String, a2: String, a3: String) case class

    B(b1: String, b2: String, b3: String) def listToCsv(list: List[String]): String
  4. 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)
  5. import shapeless._ val h = "ScalaTaiwan" :: 18 :: true

    :: HNil // h: String :: Int :: Boolean :: HNil val isAwesome = h.last // isAwesome: Boolean = true h(3) // <console>: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.
  6. h.head h.tail h.take(2) 1 +: h h :+ 1 h

    ++ h h.map(...) h.filter(...) h.flatMap(...) 可以當 List 操作的高級 Tuple
  7. 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
  8. 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 = ???
  9. 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(",") }
  10. 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)
  11. 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: ...不是跟剛剛一樣嗎! 還更長了!
  12. 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: ...不是跟剛剛一樣嗎! 還更長了!
  13. 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]
  14. 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])
  15. 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]
  16. 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]
  17. 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]
  18. 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]
  19. 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]
  20. 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]
  21. 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) }
  22. 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) }
  23. 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
  24. 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]
  25. 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]
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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)) }
  32. case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan",

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

    18) val json = classToJson(meetup) //json: String = {"name":"ScalaTaiwan","num":18}
  34. 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
  35. 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 + "}" }
  36. 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 + "\"" }
  37. 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]
  38. 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]
  39. 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]
  40. case class Meetup(name: String, num: Int) val meetup = Meetup("ScalaTaiwan",

    18) val json = classToJson(meetup) //json: String = {"name":"ScalaTaiwan","num":18}
  41. 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 }
  42. 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) }
  43. 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)
  44. 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)) }
  45. 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
  46. 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
  47. 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