Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the name of a case class field as a string/symbol at compile time using shapeless?

I'd like to somehow get at compile time the name of a field of a case class in a val (possibly a singleton-typed string or symbol?).

Something like the following:

import shapeless._
case class MyClass(field1: String, field2: Int)
val field1Lens = lens[MyClass] >> 'field1 
// val name = field1Lens.name // it should be "field1", aka 'field1.name

I don't have to necessarily use lenses, any technique that works is fine (something with LabelledGeneric?). I would like to have something where I can get the name of the case class field without specifying it separately. This way, if I refactor the name of the field1 member in the class, name changes accordingly.

Of course the following doesn't work because the macro doesn't know at compile time the name of the symbol:

val name = 'field1
val field1Lens = lens[MyClass] >> name // can't possibly work

I tried lens[MyClass] >> name.narrow but it doesn't work either

This is what I'm currently doing, and of course I don't like it:

// If I change the name of the field, compilation fails 
// and I'm forced to check this line of code so I can change the string in the line below
protected val fieldNameCheck = lens[X].someField
val someField = "someField"

edit: Ok, I gave a look at gabriele's question, and by using Keys I'm able to get an HList containing the (tagged) keys of the record. What I need though is getting one specific field, not a list containing all of them.

I'm trying to use select to get a particular key but I haven't succeeded so far

import shapeless._
import shapeless.syntax.singleton._
import shapeless.ops.record._

case class Foo(bar: String, baz: Boolean)
val labl = LabelledGeneric[Foo]
val keys = Keys[labl.Repr].apply

// the following doesn't work
// val bar = 'bar.narrow
// keys.select[bar.type]
// keys.get('bar)
like image 892
Giovanni Caporaletti Avatar asked Mar 15 '16 09:03

Giovanni Caporaletti


2 Answers

The argument to >> is a Witness that captures the member name as a compile-time symbol. When you write >> 'bar, the symbol literal is implicitly converted to a Witness, which is usually what you want, but you can also provide one yourself:

scala> case class Foo(bar: String, baz: Boolean)
defined class Foo

scala> val barKey = shapeless.Witness('bar)
barKey: shapeless.Witness.Aux[shapeless.tag.@@[Symbol,String("bar")]] = ...

scala> shapeless.lens[Foo] >> barKey
res0: shapeless.Lens[Foo,String] = shapeless.Lens$$anon$7@344bfb60

As I mention in a comment above, you may also be interested in the positional selectors:

scala> shapeless.lens[Foo] >> shapeless.nat._1
res1: shapeless.Lens[Foo,Boolean] = shapeless.Lens$$anon$7@16e9d434

Or even just:

scala> shapeless.lens[Foo] >> 1
res2: shapeless.Lens[Foo,Boolean] = shapeless.Lens$$anon$7@4be29007

These don't require you to write the member name anywhere in your code, although you'll run into trouble if you rearrange members.

like image 185
Travis Brown Avatar answered Oct 30 '22 14:10

Travis Brown


Ok, thanks to Travis' comments I got it working:

import shapeless._
case class MyClass(field1: String, field2: Int)

def fieldName[A](fieldKey: Witness.Lt[_ <: Symbol])(implicit mkl: MkFieldLens[A, fieldKey.T]) = {
  lens[A] >> fieldKey
  fieldKey.value.name
}

println(fieldName[MyClass]('field1))
like image 43
Giovanni Caporaletti Avatar answered Oct 30 '22 14:10

Giovanni Caporaletti