Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LabelledGeneric to get class name

I'm fairly new to Shapeless, as one will infer from my question. Given an instance of LabelledGeneric, how do I get the name of the class that it represents. I can get the field name information from Keys, so I assume I need some other kind of Witness that encapsulates the type itself, but I cannot figure out which.

Eg, if I have a case class called Foo in package com.bar, I want to get the string "com.bar.Foo" (or separately is fine).

implicit def example[T, Repr <: HList](implicit label: LabelledGeneric.Aux[T, Repr],
                                         kk: Keys[Repr]): Bibble[T] = new Bibble[T] {
  override def typeName(value: T): String = ???
}
like image 688
sksamuel Avatar asked Mar 14 '23 00:03

sksamuel


1 Answers

Shapeless's Generic provides a sum-of-products representation for case classes and sealed traits, which means that if we have a simple ADT like this:

sealed trait Base
case object Foo extends Base
case class Bar(i: Int, s: String) extends Base

Then Generic[Base] will give us a mapping to a Foo.type :+: Bar :+: CNil—i.e. a Foo.type or a Bar (where the or means we're talking about a "sum type" in type theoretic terms), and a Generic[Bar] gives us a mapping to an Int :: String :: HNil, which is Int and a String (a product type, where "product" has roughly the same meaning that it does in the case of the scala.ProductN types in the standard library).

LabelledGeneric uses an enhanced version of the sum-of-products representation, where each of the terms in the product or sum is tagged with a label. In the case of a sealed trait, these will be the constructor names for each subtype, and in the case of a case class they'll be the member names. These aren't fully-qualified names—just labels that disambiguate locally.

Generic and LabelledGeneric aren't intended to serve as general-purpose tools for compile-time reflection. They're not available for arbitrary types, for example, and they don't provide access to the name of the type itself.

Your best bet is probably to use TypeTag, but if you want a type-level representation of the name (like LabelledGeneric provides for labels), you'll need to define your own type class with macro-generated instances. Something like the following should work:

import scala.language.experimental.macros
import scala.reflect.macros.whitebox.Context

trait TypeInfo[A] { type Name <: String; def name: String }

object TypeInfo {
  type Aux[A, Name0 <: String] = TypeInfo[A] { type Name = Name0 }

  def apply[A](implicit ti: TypeInfo[A]): Aux[A, ti.Name] = ti

  implicit def materializeTypeInfo[A, Name <: String]: Aux[A, Name] =
    macro matTypeInfoImpl[A, Name]

  def matTypeInfoImpl[A: c.WeakTypeTag, Name <: String](c: Context): c.Tree = {
    import c.universe._

    val A = c.weakTypeOf[A]
    val name = A.typeSymbol.name.decodedName.toString.trim

    q"new TypeInfo[$A] { type Name = ${ Constant(name) }; def name = $name }"
  }
}

This is probably overkill for your use case, though, if you only need value-level strings.

like image 133
Travis Brown Avatar answered Mar 26 '23 01:03

Travis Brown