score:2

Accepted answer

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

scastie

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.


Related Query

More Query from same tag