is a library for generic programming in Scala, largely , but mostly behind the scenes; it is likely shapeless powers some of the libraries in your project, even though you don’t use it directly. Shapeless present in the ecosystem Trying to solve an everyday problem, I’ve found an use case which I could solve using shapeless. This post doesn’t intend to explain how shapeless works (there is a ), but to provide a taste of it instead. whole book about it here The Challenge We need to write a REST API consumer using and . Here is the service definition: ZIO http4s io.circe. zio.{ , , } { = [ ] { rootUrl = [ ](uri: , parameters: [ , ]) ( d: [ ]): [ [ ]] } [ ](resource: , parameters: [ , ]) ( d: [ ]): [ , [ ]] = .accessM[ ](_.get.get[ ](resource, parameters)) } import Decoder import Has RIO Task object HttpClient type HttpClient Has Service trait Service protected final val "http://localhost:8080" def get T String Map String String implicit Decoder T Task List T def get T String Map String String implicit Decoder T RIO HttpClient List T RIO HttpClient T In case you are not familiar with ZIO (you should, it’s awesome), what you need to know is: every get requests returns a of Task List the get function outside the is just a helper to access the environment of the effect (ZIO stuff) Service . Learn more about ZIO modules and layers here This is the implementation, using : HttpClient.Service http4s io.circe. org.http4s. org.http4s.circe. .circeEntityDecoder org.http4s.client. org.http4s.client.dsl. zio._ zio.interop.catz._ . [ ] { [ ](resource: , parameters: [ , ]) ( d: [ ]): [ [ ]] = { uri = (path = rootUrl + resource) .withQueryParams(parameters) client .expect[ [ ]](uri.toString()) .foldM( .fail(_), .succeed(_)) } } import Decoder import Uri import CirceEntityCodec import Client import Http4sClientDsl import import ( ) class Http4sClient client: [ ] Client Task extends HttpClient Service with Http4sClientDsl Task def get T String Map String String implicit Decoder T Task List T val Uri List T IO ZIO adds to the and the are the query string. Now, to represent the request call, we have a case class called : Http4sClient.get resource uri parameters OrganisationRequest case ( ) class OrganisationRequest code: [ ], description: [ ], page: = 1 Option String Option String Integer The Problem Using the client (via helper) is trivial, except for one detail: get .get (request: ): get[ ]( , ???) import HttpClient def organisations OrganisationRequest Organisation "/organisations" We need to transform the into , what is an easy task. However, there are many “request” objects, and writing methods to every single one of them is a Java-ish solution. Here is the challenge: how can we build this generic transformation? request Map[String, String] toMap Spoiler: with shapeless. A bit of shapeless This section is a grasp of how shapeless works, so the solution will make more sense when we get there. Shapeless can create an (or ) as a generic representation of case classes. Let’s do it using : Heterogenous List HList Generic scala> shapeless._ scala> org = ( ( ), , ) org: = ( (org), , ) scala> gen = [ ] gen: shapeless. [ ] { = [ ] :: [ ] :: :: shapeless. } = anon$macro$ $ @ f146f2 scala> gen.to(org) res8: gen. = (acme) :: :: :: import val OrganisationRequest Some "acme" None 5 OrganisationRequest OrganisationRequest Some None 5 val Generic OrganisationRequest Generic OrganisationRequest type Repr Option String Option String Integer HNil 4 1 48 Repr Some None 5 HNil The generic representation of is an of type . We have the values, but we need the names of the fields for our . We need instead of : OrganisationRequest HList Option[String] :: Option[String] :: Int :: HNil Map LabelledGeneric Generic scala> lgen = [ ] lgen: shapeless. [ ] { = [ ] shapeless.labelled. [ shapeless.tag. [ ( )], [ ]] :: [ ] shapeless.labelled. [ shapeless.tag. [ ( )], [ ]] :: shapeless.labelled. [ shapeless.tag. [ ( )], ] :: shapeless. } = shapeless. $$anon$ @ f78c67 val LabelledGeneric OrganisationRequest LabelledGeneric OrganisationRequest type Repr Option String with KeyTag Symbol with Tagged String "code" Option String Option String with KeyTag Symbol with Tagged String "description" Option String Integer with KeyTag Symbol with Tagged String "page" Integer HNil LabelledGeneric 1 55 As you can see, with it’s possible to retain the information about the field names as well. LabelledGeneric The Solution Luckily, we don’t need to manipulate ourselves, shapeless provides us with plenty of useful type classes that can be found in the package. We will build our solution using : LabelledGeneric shapless.ops ToMap scala> shapeless.ops.product. scala> toMap = [ ] toMap: shapeless.ops.product. [ ] { = shapeless.tag. [_ >: ( ) ( ) ( ) <: ]; = java.io. } = shapeless.ops.product$ $$anon$ @ bccd311 scala> map = toMap(org) map: toMap. = ( -> , -> , -> (acme)) import ToMap val ToMap OrganisationRequest ToMap OrganisationRequest type K Symbol with Tagged String "page" with String "description" with String "code" String type V Serializable ToMap 5 3 val Out Map 'page 5 'description None 'code Some We can make it even nicer using shapeless syntax: scala> shapeless.syntax.std.product._ scala> map = org.toMap[ , ] map: [ , ] = ( -> , -> , -> (acme)) import val Symbol Any Map Symbol Any Map 'page 5 'description None 'code Some For the final solution, let’s create an in order to add a method to our class. Besides, we should remove every entry with or values, flatten the and turn keys and values into : implicit class parameters request null None Options String shapeless.ops.product. shapeless.syntax.std.product._ { ( toMap: . [ , , ]): [ , ] = a.toMap[ , ] .filter { (_, v: [ ]) => v.isDefined (_, v) => v != } .map { (k, v: [ ]) => k.name -> v.get.toString (k, v) => k.name -> v.toString } } import ToMap import implicit [ <: ]( ) class RequestOps A Product val a: A def parameters implicit ToMap Aux A Symbol Any Map String String Symbol Any case Option Any case null case Option Any case A few comments here: needs to be in place so we can use . , it’s just a matter of adding the constrain for implicit resolution; A <: Product shapeless.ops.product All case classes implement Product the implicit parameter is a instead of just . Long story short, shapeless defines the alias in order to make some of its internal complexity more readable and usable. Just trust me here ;) toMap ToMap.Aux ToMap Aux Finally, this brings us to an elegant solution: .get (request: ): get[ ]( , request.parameters) import HttpClient import RequestOps def organisations OrganisationRequest Organisation "/organisations" Conclusion Even though shapeless looks almost magical at the first glance, after dedicating myself to understand it better, I’ve figured it can be very useful in practical terms. Shapeless provides a broad range of typeclasses that can be used in all sort of ways, and spending time learning about how they work is a very interesting exercise, improving skills related to typeclasses, derivations and bringing clarity about how some popular libraries that use shapeless work, like . circe I’ve heard that adding too much shapeless can properly affect the project’s compile time. I’d like to hear more about it, if you have experience using shapeless directly, please share in the comments. Originally published at https://juliano-alves.com on April 6, 2020.