Shapeless in the real world: An introduction to generic programming

Shapeless in the real world: An introduction to generic programming

Shapeless is a powerful Scala library used behind many others, such as Circe or Doobie. This talk will show you how shapeless works, using it to solve a real problem.

6b8d8f84c065846e8ec1f12f67d79991?s=128

Juliano Alves

May 06, 2020
Tweet

Transcript

  1. Juliano Alves @vonjuliano juliano-alves.com Shapeless in the real world: An

    introduction to generic programming.
  2. Who am I? • Software Engineer, Searcher of perfect modularization,

    Lover of Functional Languages • The cool ones Scala, Clojure, Elixir • The "vintage" ones Java, C#, Python, Ruby @vonjuliano juliano-alves.com
  3. https://getquill.io

  4. None
  5. https://github.com/juliano/pdt-client

  6. // ZIO Service object HttpClient { type HttpClient = Has[Service]

    trait Service { protected final val rootUrl = "http://www.transparencia.gov.br/api-de-dados/" def get[T](uri: String, parameters: Map[String, String]) (implicit d: Decoder[T]): Task[T] }
  7. // ZIO Service // Helpers def get[T](resource: String, parameters: Map[String,

    String] = Map()) (implicit d: Decoder[T]): RIO[HttpClient, List[T]] = RIO.accessM[HttpClient]( _.get.get[List[T]](resource, parameters)) def get[T](resource: String, id: Long) (implicit d: Decoder[T]): RIO[HttpClient, T] = RIO.accessM[HttpClient]( _.get.get[T](s"$resource/$id", Map()))
  8. // Http4s Implementation private[client] final case class Http4s(client: Client[Task]) extends

    HttpClient.Service with Http4sClientDsl[Task] { def get[T](resource: String, parameters: Map[String, String]) (implicit d: Decoder[T]): Task[T] = { val uri = Uri(path = rootUrl + resource) .withQueryParams(parameters) client .expect[T](uri.toString()) .foldM(IO.fail(_), ZIO.succeed(_)) } }
  9. // ZIO Service object HttpClient { // … def http4s:

    URLayer[Has[Client[Task]], Has[Service]] = ZLayer.fromService[Client[Task], Service] { http4sClient: Client[Task] => Http4s(http4sClient) } } Module Pattern
  10. https://zio.dev/docs/howto/howto_use_layers

  11. What is the problem?

  12. // ZIO Service object HttpClient { type HttpClient = Has[Service]

    trait Service { protected final val rootUrl = "http://www.transparencia.gov.br/api-de-dados/" def get[T](uri: String, parameters: Map[String, String]) (implicit d: Decoder[T]): Task[T] } // Algum cliente def siafi(request: OrgaoRequest): RIO[HttpClient, List[OrgaoSiafi]] = get[OrgaoSiafi]("orgaos-siafi", ???)
  13. // domain.scala case class OrgaoRequest(codigo: Option[String], descricao: Option[String], pagina: Int

    = 1) case class OrgaoSiafi(codigo: String, codigoDescricaoFormatado: String, descricao: String) case class OrgaoSiape(codigo: String, codigoDescricaoFormatado: String, descricao: String)
  14. // domain.scala case class OrgaoRequest(codigo: Option[String], descricao: Option[String], pagina: Int

    = 1) { def toMap: Map[String, String] = ??? } case class OrgaoSiafi(codigo: String, codigoDescricaoFormatado: String, descricao: String) case class OrgaoSiape(codigo: String, codigoDescricaoFormatado: String, descricao: String)
  15. None
  16. // domain.scala case class OrgaoRequest(codigo: Option[String], descricao: Option[String], pagina: Int

    = 1) { def toMap: Map[String, String] = Map("codigo" -> "1337", "descricao" -> "Orgão Federal da Carreta Furacão", "pagina" -> "1") }
  17. None
  18. Show me the code

  19. // implicits.scala package object implicits { implicit class HttpRequestOps[A <:

    Product](val a: A) { def parameters(implicit toMap: ToMap.Aux[A, Symbol, Any]): Map[String, String] = a.toMap[Symbol, Any] .filter { case (_, v: Option[Any]) => v.isDefined case (_, v) => v != null } .map { case (k, v: Option[Any]) => k.name -> v.get.toString case (k, v) => k.name -> v.toString } } }
  20. // ZIO Service object HttpClient { type HttpClient = Has[Service]

    trait Service { protected final val rootUrl = "http://www.transparencia.gov.br/api-de-dados/" def get[T](uri: String, parameters: Map[String, String]) (implicit d: Decoder[T]): Task[T] }
  21. // domain.scala case class OrgaoRequest(codigo: Option[String], descricao: Option[String], pagina: Int

    = 1) { def toMap: Map[String, String] = ??? }
  22. object Orgaos { def siafi(request: OrgaoRequest): RIO[HttpClient, List[OrgaoSiafi]] = get[OrgaoSiafi]("orgaos-siafi",

    ???) } object CEISs { def by(request: CEISRequest): RIO[HttpClient, List[CEIS]] = get[CEIS]("ceis", ???) }
  23. import pdt.implicits.HttpRequestOps object Orgaos { def siafi(request: OrgaoRequest): RIO[HttpClient, List[OrgaoSiafi]]

    = get[OrgaoSiafi]("orgaos-siafi", HttpRequestOps(request).parameters) } object CEISs { def by(request: CEISRequest): RIO[HttpClient, List[CEIS]] = get[CEIS]("ceis", HttpRequestOps(request).parameters) }
  24. More about shapeless • https://juliano-alves.com/2020/04/06/shapeless-a-real-world-use-case/ The post which inspired this

    talk • https://underscore.io/books/shapeless-guide/ The Type Astronaut's guide to shapeless
  25. Conclusion

  26. Questions?

  27. Juliano Alves @vonjuliano juliano-alves.com Shapeless in the real world: An

    introduction to generic programming. Thank you!