Reputation: 127
I have been struggling for a couple days already to try to create a macro or use shapeless to create a method/function to extract field names and values as a Tuple[String, String].
Lets imagine the following case class:
case class Person(name: String, age: Int)
I want to have something like this (doesn't really need to be a method in case class).
case class Person(name: String, age: Int) {
def fields: List[(String, String)] = ???
}
// or
def fields[T](caseClass: T): List[(String, String)] = ???
I've seen quite few similar solutions here but I can't make it work with my use case of (String, String)
I would also appreciate some literature to learn and expand my knowledge regarding macros, I have both Programming in Scala(Third Edition by Martin) and Programming Scala (O'REILLY - Dean Wampler & Alex Payne) and only O'REILLY has a very small chapter regarding macros and to be honest its very lacking.
PD: I'm using Scala 2.12.12 so I don't have those fancy new methods for case class productElementNames and such :(
Upvotes: 2
Views: 1715
Reputation: 48400
Based on LabelledGeneric
and Keys
type classes
import shapeless.LabelledGeneric
import shapeless.HList
import shapeless.ops.hlist.ToTraversable
import shapeless.ops.record.Keys
case class Person(name: String, age: Int)
def fields[P <: Product, L <: HList, R <: HList](a: P)(
implicit
gen: LabelledGeneric.Aux[P, L],
keys: Keys.Aux[L, R],
ts: ToTraversable.Aux[R, List, Symbol]
): List[(String, String)] = {
val fieldNames = keys().toList.map(_.name)
val values = a.productIterator.toList.map(_.toString)
fieldNames zip values
}
fields(Person("Jean-Luc, Picard", 70))
// : List[(String, String)] = List((name,Jean-Luc, Picard), (age,70))
IDEA ... shows an error ... No implicit arguments
IntelliJ in-editor error highlighting is sometimes not 100% accurate when it comes to type-level code and macros. Best is to consider it as just guidance, and put trust in the Scala compiler proper, so if compiler is happy but IJ is not, then go with the compiler. Another options is to try Scala Metals which should have one-to-one mapping between compiler diagnostics and in-editor error highlighting.
why you used LabelledGeneric.Aux, Keys.Aux, ToTraversable.Aux
This is using a design pattern called type classes. My suggestion would be to work through The Type Astronaut's Guide to Shapeless in particular section on Chaining dependent functions
Dependently typed functions provide a means of calculating one type from another. We can chain dependently typed functions to perform calculations involving multiple steps.
Consider the following dependency between types
input type
|
gen: LabelledGeneric.Aux[P, L],
|
output type
input type
|
keys: Keys.Aux[L, R]
|
output type
Note how for example the output type L
of LabelledGeneric
becomes the input type of Keys
. In this way you are showing the compiler the relationship between the types and in return the compiler is able to give your an HList
representing the field names from Product
representing the particular case class, and all this before the program even runs.
ToTraversable
is needed so you can get back a regular Scala List
from an HList
which enables the following bit
.toList.map(_.name)
Hopefully this gives you at least a little bit of direction. Some keywords to search for are: type classes, dependent types, implicit resolution, type alias Aux pattern, type members vs type parameters, type refinement, etc. Typelevel community has a new Discord channel where you can get further direction.
Upvotes: 3