Slide 1

Slide 1 text

Juliano Alves @vonjuliano juliano-alves.com Shapeless in the real world: An introduction to generic programming.

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

https://getquill.io

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

https://github.com/juliano/pdt-client

Slide 6

Slide 6 text

// 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] }

Slide 7

Slide 7 text

// 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()))

Slide 8

Slide 8 text

// 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(_)) } }

Slide 9

Slide 9 text

// ZIO Service object HttpClient { // … def http4s: URLayer[Has[Client[Task]], Has[Service]] = ZLayer.fromService[Client[Task], Service] { http4sClient: Client[Task] => Http4s(http4sClient) } } Module Pattern

Slide 10

Slide 10 text

https://zio.dev/docs/howto/howto_use_layers

Slide 11

Slide 11 text

What is the problem?

Slide 12

Slide 12 text

// 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", ???)

Slide 13

Slide 13 text

// 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)

Slide 14

Slide 14 text

// 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)

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

// 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") }

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Show me the code

Slide 19

Slide 19 text

// 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 } } }

Slide 20

Slide 20 text

// 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] }

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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", ???) }

Slide 23

Slide 23 text

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) }

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Conclusion

Slide 26

Slide 26 text

Questions?

Slide 27

Slide 27 text

Juliano Alves @vonjuliano juliano-alves.com Shapeless in the real world: An introduction to generic programming. Thank you!