class: center, middle #
A tour of Typelevel
by way of Circe
Travis Brown
@travisbrown
--- layout: true --- .left-column[ ## Intro ] .right-column[ ### Some of the things we'll be looking at * [Cats](https://github.com/typelevel/cats) * Functors and contravariant functors * Applicative and monadic composition * The state monad * [Shapeless](https://github.com/milessabin/shapeless) * Generic representations * Hlists and coproducts * [Typelevel Scala](https://typelevel.org/scala/): Singleton types * [Refined](https://github.com/fthomas/refined): Type-level constraints * [Monocle](https://github.com/julien-truffaut/Monocle): Paths as values * [fs2](https://github.com/functional-streams-for-scala/fs2): Streaming parsing and decoding * [ScalaCheck](https://github.com/rickynils/scalacheck): Generating arbitrary JSON ] --- .left-column[ ## Intro ## Type classes ] .right-column[ ### Ad-hoc polymorphism (type classes) Foundational concept for every one of these libraries ] --- .left-column[ ## Intro ## Type classes ] .right-column[ ### Ad-hoc polymorphism (type classes) Foundational concept for every one of these libraries * `io.circe.Decoder[A]` * `shapeless.LabelledGeneric[A]` * `eu.timepit.refined.Validate[A, P]` * `monocle.function.Index[S, I, A]` * `org.scalacheck.Arbitrary[A]` * `io.finch.Encode[A]` ] --- .left-column[ ## Intro ## Type classes ] .right-column[ ### Ad-hoc polymorphism (type classes) Foundational concept for every one of these libraries * `io.circe.Decoder[A]` * `shapeless.LabelledGeneric[A]` * `eu.timepit.refined.Validate[A, P]` * `monocle.function.Index[S, I, A]` * `org.scalacheck.Arbitrary[A]` * `io.finch.Encode[A]` And also… * `cats.Monad[F[_]]` * `cats.effect.Effect[F[_]]` ] --- .left-column[ ## Intro ## Type classes ] .right-column[ ### Subtype polymorphism ```scala import io.circe.Json trait Encodable { def toJson: Json } case class Location(city: String, nation: String) extends Encodable { def toJson: Json = Json.obj( "city" -> Json.fromString(city), "nation" -> Json.fromString(nation) ) } case class User(name: String, age: Int, location: Location) extends Encodable { def toJson: Json = Json.obj( "name" -> Json.fromString(name), "age" -> Json.fromInt(age), "location" -> location.toJson ) } def printJson[A <: Encodable](a: A): Unit = println(a.toJson.noSpaces) ``` ] --- .left-column[ ## Intro ## Type classes ] .right-column[ ### Ad-hoc polymorphism ```scala import io.circe.Json trait Encoder[A] { def apply(a: A): Json } case class Location(city: String, nation: String) case class User(name: String, age: Int, location: Location) implicit val encodeLocation: Encoder[Location] = new Encoder[Location] { def apply(a: Location): Json = Json.obj( "city" -> Json.fromString(a.city), "nation" -> Json.fromString(a.nation) ) } implicit val encodeUser: Encoder[User] = new Encoder[User] { def apply(a: User): Json = Json.obj( "name" -> Json.fromString(a.name), "age" -> Json.fromInt(a.age), "location" -> encodeLocation(a.location) ) } def printJson[A: Encoder](a: A): Unit = println(implicitly[Encoder[A]].apply(a).noSpaces) ``` ] --- .left-column[ ## Intro ## Type classes ] .right-column[ ### Two flavors of polymorphism Subtype polymorphism: * A is a B Ad-hoc polymorphism: * A has an operation B ] --- .left-column[ ## Intro ## Type classes ## Circe tips ] .right-column[ ### Circe syntactic helpers JSON literals: ```scala import io.circe.literal._ val doc = json"[1, 2, 3]" ``` Extension methods for conversion to JSON: ```scala import io.circe.syntax._ val doc = List(1, 2, 3).asJson ``` Getting an instance of an encoder or decoder: ```scala import io.circe.Decoder val decoderForString = Decoder[String] ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ] .right-column[ ### Decoding with Circe Decoding a JSON value directly from a decoder: ```scala decoderForString.decodeJson(json""""some JSON string"""") res0: io.circe.Decoder.Result[String] = Right(some JSON string) ``` Parsing and decoding a JSON value: ```scala import io.circe.jawn jawn.decode[List[Int]]("[1, 2, 3, 4]") ``` Parsing and decoding a JSON value with error accumulation: ```scala jawn.decodeAccumulating[List[Int]]("[1, 2, 3, 4]") jawn.decodeAccumulating[List[Int]]("[1, 2, true, 3, 4, 5.6]") ``` Providing a decoder explicitly for parsing and decoding: ```scala jawn.decode(""""some JSON string"""")(decoderForString) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### Composing instances of our type classes * Functors and contravariant functors ```scala import io.circe.{ Decoder, Encoder } case class UserId(value: String) val userId = "travisbrown" implicit val encodeUserId: Encoder[UserId] = ??? implicit val decodeUserId: Decoder[UserId] = ??? ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### Composing instances of our type classes * Applicative functors and monads ```scala case class User(first: String, last: String, age: Int) val good = """{"first":"Foo","last":"McBar","age":25}""" val bad = """{"first":true,"age":25.1}""" val decodeFirst = Decoder[String].prepare(_.downField("first")) val decodeLast = Decoder[String].prepare(_.downField("last")) val decodeAge = Decoder[Int].prepare(_.downField("age")) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### Monadic composition ```scala val decodeUserMonadic: Decoder[User] = for { f <- decodeFirst l <- decodeLast a <- decodeAge } yield User(f, l, a) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### Monadic composition ```scala val decodeUserMonadic: Decoder[User] = for { f <- decodeFirst l <- decodeLast a <- decodeAge } yield User(f, l, a) ``` Without the syntax: ```scala val decodeUserMonadicDesugared: Decoder[User] = decodeFirst.flatMap { f => decodeLast.flatMap { l => decodeAge.map(a => User(f, l, a)) } } ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### Monadic composition ```scala val decodeUserMonadic: Decoder[User] = for { f <- decodeFirst l <- decodeLast a <- decodeAge } yield User(f, l, a) ``` vs. applicative composition: ```scala val decodeUserApplicative: Decoder[User] = (decodeFirst, decodeLast, decodeAge).mapN(User(_, _, _)) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ # [Exercise 1!](https://github.com/travisbrown/typelevel-tour/blob/master/src/main/scala/Exercises.scala#L4) ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### Validated ```scala import cats.data.Validated.Invalid val Invalid(failures) = io.circe.jawn.decodeAccumulating("{}")(decodeUserApplicative) failures.toList.foreach(println) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ ### The state monad ```scala val decodeUserNoExtras: Decoder[User] = Decoder.fromState( for { f <- Decoder.state.decodeField[String]("first") l <- Decoder.state.decodeField[String]("last") a <- Decoder.state.decodeField[Int]("age") _ <- Decoder.state.requireEmpty } yield User(f, l, a) ) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ] .right-column[ # [Exercise 2!](https://github.com/travisbrown/typelevel-tour/blob/master/src/main/scala/Exercises.scala#L20) ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ] .right-column[ ### Generic derivation ```scala case class User(first: String, last: String, age: Int) import io.circe.generic.auto._, io.circe.syntax._ User("Foo", "McBar", 25).asJson // res0: String = {"first":"Foo","last":"McBar","age":25} ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ] .right-column[ ### More advanced generic derivation ```scala val partialDoc = """{ "first": "Foo", "last": "McBar" }""" val result = jawn.decode[Int => User](partialDoc) val Right(user) = result.map(_(25)) // user: User = User(Foo,McBar,25) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ] .right-column[ ### More configurable generic derivation ```scala import io.circe.generic.extras.Configuration import io.circe.generic.extras.auto._ implicit val circeConfig: Configuration = Configuration.default.withSnakeCaseMemberNames case class User(firstName: String, lastName: String) val doc = """{ "first_name": "Foo", "last_name": "McBar" }""" jawn.decode[User](doc) // res2: Either[io.circe.Error,User] = Right(User(Foo,McBar)) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ] .right-column[ # [Exercise 3!](https://github.com/travisbrown/typelevel-tour/blob/master/src/main/scala/Exercises.scala#L39) ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ] .right-column[ ### Decoding records with circe-shapes ```scala import io.circe.shapes._ import shapeless._, shapeless.labelled.FieldType type Rec = FieldType["BLKLOT", String] :: FieldType["BLOCK_NUM", String] :: FieldType["LOT_NUM", String] :: HNil Decoder[Rec].decodeJson(CityLots.sampleProperties) // Right(0001001 :: 0001 :: 001 :: HNil) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ] .right-column[ ### Decoding unions with circe-shapes ```scala import io.circe.shapes._ import shapeless._, shapeless.labelled.FieldType type Rec = FieldType["name", String] :+: FieldType["count", Int] :+: FieldType["error", String] :+: CNil val doc = """[ { "name": "xyz" }, { "name": "abc" }, { "count": 100 } ]""" jawn.decode[List[Rec]](doc) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ] .right-column[ ### The Typelevel Scala compiler Literals as types: ```scala type Foo = "foo" type I10 = 10 ``` ([Coming soon](https://twitter.com/milessabin/status/938457896022675456) to Lightbend Scala…) ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ] .right-column[ ### The Typelevel Scala compiler Literals as types: ```scala type Foo = "foo" type I10 = 10 ``` ([Coming soon](https://twitter.com/milessabin/status/938457896022675456) to Lightbend Scala…) In the meantime: ```scala import shapeless.Witness type Foo = Witness.`"foo"`.T type I10 = Witness.`10`.T ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ] .right-column[ ### Decoding constants with circe-literal and TLS ```scala scala> import io.circe.jawn, io.circe.literal._ import io.circe.jawn import io.circe.literal._ scala> jawn.decode[1]("1") res0: Either[io.circe.Error,1] = Right(1) scala> jawn.decode[1]("10") res1: Either[io.circe.Error,1] = Left(DecodingFailure(Int(1), List())) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ] .right-column[ ### Refined Type-level constraints on values: ```scala import eu.timepit.refined._ import eu.timepit.refined.api.Refined import eu.timepit.refined.auto._ import eu.timepit.refined.numeric._ case class User( first: String, last: String, age: Refined[Int, Positive] ) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ] .right-column[ ### Refined decoders Type-level constraints on values: ```scala import io.circe.generic.auto._ import io.circe.refined._ val good = """{"first":"Foo","last":"McBar","age":25}""" val bad = """{"first":"Foo","last":"McBar","age":-25}""" jawn.decode[User](good) jawn.decode[User](bad) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ] .right-column[ # [Exercise 4!](https://github.com/travisbrown/typelevel-tour/blob/master/src/main/scala/Exercises.scala#L60) ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ## Monocle ] .right-column[ ### Paths as values ```scala scala> import io.circe.optics.JsonPath import io.circe.optics.JsonPath scala> val path = JsonPath.root.index(3).properties.MAPBLKLOT path: io.circe.optics.JsonPath = JsonPath(monocle.POptional$$anon$1@5d3c682) scala> path.json.getOption(CityLots.sampleData) res0: Option[io.circe.Json] = Some("0005001") ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ## Monocle ] .right-column[ # [Exercise 5!](https://github.com/travisbrown/typelevel-tour/blob/master/src/main/scala/Exercises.scala#L97) ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ## Monocle ## fs2 ] .right-column[ ### Streaming parsing ```scala import cats.effect.IO import _root_.fs2.Stream import io.circe.Json import io.circe.fs2.byteArrayParser val parsedStream: Stream[IO, Json] = CityLots.streamingData.through(byteArrayParser) ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ## Monocle ## fs2 ## ScalaCheck ] .right-column[ ### Arbitrary JSON values ```scala import io.circe.Json import io.circe.testing.instances._ import org.scalacheck.{ Arbitrary, Gen } val genJson: Gen[Json] = Arbitrary.arbitrary[Json] ``` ] --- .left-column[ ## Intro ## Type classes ## Circe tips ## Cats ## Shapeless ## TLS ## Refined ## Monocle ## fs2 ## ScalaCheck ] .right-column[ ### Thanks! ]