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

Shapeless Introduction

Shapeless Introduction

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