Home > front end >  Getting Case Class definition which points to another Case Class
Getting Case Class definition which points to another Case Class

Time:02-01

I am looking at getting case class definitions.

From SO I gleaned this practice as per Get field names list from case class, the answer using reflection by Dia Kharrat.

Some experimenting in which I have a case class referring to another case class, nested. Can we get the metadata expanded easily in some way?

import scala.collection.mutable.ArrayBuffer

case class MyChgClass(b: Option[String], c: Option[String], d: Option[String])
case class MyFullClass(k: Int, b: String, c: String, d: String)
case class MyEndClass(id: Int, after: MyFullClass)

def classAccessors[T: TypeTag]: List[MethodSymbol] = typeOf[T].members.collect {
 case m: MethodSymbol if m.isCaseAccessor => m
}.toList

val z1 = classAccessors[MyChgClass]
val z2 = classAccessors[MyFullClass]
val z3 = classAccessors[MyEndClass]

returns:

z1: List[reflect.runtime.universe.MethodSymbol] = List(value d, value c, value b)
z2: List[reflect.runtime.universe.MethodSymbol] = List(value d, value c, value b, value k)
z3: List[reflect.runtime.universe.MethodSymbol] = List(value after, value id)

So:

  1. Looking to expand the case class MyEndClass.
  2. The option aspect appears not not been supplied. Possible?

CodePudding user response:

  1. The option aspect appears not not been supplied. Possible?

Are you looking for .name and .typeSignature?

val z1 = classAccessors[MyChgClass]
val z2 = classAccessors[MyFullClass]
val z3 = classAccessors[MyEndClass]

z1.map(_.name) // List(d, c, b)
z1.map(_.typeSignature) // List(Option[String], Option[String], Option[String])
z2.map(_.name) // List(d, c, b, k)
z2.map(_.typeSignature) // List(String, String, String, Int)
z3.map(_.name) // List(after, id)
z3.map(_.typeSignature) // List(MyFullClass, Int)

If your classes are known at compile time it would make sense to use compile-time reflection i.e. macros rather than runtime reflection

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def classAccessors[T]: List[(String, String)] = macro classAccessorsImpl[T]

def classAccessorsImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
  import c.universe._
  val pairs = weakTypeOf[T].members.collect {
    case m: MethodSymbol if m.isCaseAccessor => m
  }.map(m => (m.name.toString, m.typeSignature.toString))
  q"List.apply[(String, String)](..$pairs)"
}
// in a different subproject
classAccessors[MyChgClass] // List((d,Option[String]), (c,Option[String]), (b,Option[String]))
classAccessors[MyFullClass] // List((d,String), (c,String), (b,String), (k,Int))
classAccessors[MyEndClass] // List((after,MyFullClass), (id,Int))

Even better would be to use one of libraries encapsulating those macros into some type classes. In Shapeless the type class giving access to names and types of case-class fields is LabelledGeneric

// libraryDependencies  = "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.labelled.FieldType
import shapeless.ops.hlist.{FillWith, Mapper, ToList}
import shapeless.{HList, LabelledGeneric, Poly0, Poly1, Typeable, Witness}

object fieldNamesAndTypesPoly extends Poly1 {
  implicit def cse[K <: Symbol, V](implicit
    witness: Witness.Aux[K],
    typeable: Typeable[V]
  ): Case.Aux[FieldType[K, V], (String, String)] =
    at(_ => (witness.value.name, typeable.describe))
}

object nullPoly extends Poly0 {
  implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}

def classAccessors[T] = new PartiallyApplied[T]

class PartiallyApplied[T] {
  def apply[L <: HList, L1 <: HList]()(implicit
    labelledGeneric: LabelledGeneric.Aux[T, L],
    fillWith: FillWith[nullPoly.type, L],
    mapper: Mapper.Aux[fieldNamesAndTypesPoly.type, L, L1],
    toList: ToList[L1, (String, String)]
  ): List[(String, String)] = toList(mapper(fillWith()))
}
classAccessors[MyChgClass]() // List((b,Option[String]), (c,Option[String]), (d,Option[String]))
classAccessors[MyFullClass]() // List((k,Int), (b,String), (c,String), (d,String))
classAccessors[MyEndClass]() // List((id,Int), (after,MyFullClass))

  1. Looking to expand the case class MyEndClass.

You can try deep versions of the type classes LabelledGeneric, Mapper etc.

import shapeless.labelled.{FieldType, field}
import shapeless.{::, DepFn0, DepFn1, HList, HNil, LabelledGeneric, Poly0, Poly1, Typeable, Witness, poly}

trait DeepLabelledGeneric[T <: Product] {
  type Repr <: HList
  def to(t: T): Repr
  def from(r: Repr): T
}

object DeepLabelledGeneric {
  type Aux[T <: Product, Repr0 <: HList] = DeepLabelledGeneric[T] {type Repr = Repr0}
  def instance[T <: Product, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepLabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def deepGeneric[A <: Product, L <: HList, L1 <: HList](implicit
    labelledGeneric: LabelledGeneric.Aux[A, L],
    hListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[L, L1]
  ): Aux[A, L1] = instance(a => hListDeepLabelledGeneric.to(labelledGeneric.to(a)), l1 => labelledGeneric.from(hListDeepLabelledGeneric.from(l1)))
}

trait HListDeepLabelledGeneric[T <: HList] {
  type Repr <: HList
  def to(t: T): Repr
  def from(r: Repr): T
}

trait LowPriorityHListDeepLabelledGeneric {
  type Aux[T <: HList, Repr0 <: HList] = HListDeepLabelledGeneric[T] {type Repr = Repr0}

  def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepLabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def headNotCaseClass[H, T <: HList, T_hListDeepLGen <: HList](implicit
    tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
  ): Aux[H :: T, H :: T_hListDeepLGen] = instance({
    case h :: t => h :: tailHListDeepLabelledGeneric.to(t)
  }, {
    case h :: t => h :: tailHListDeepLabelledGeneric.from(t)
  })
}

object HListDeepLabelledGeneric extends LowPriorityHListDeepLabelledGeneric {
  implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)

  implicit def headCaseClass[K <: Symbol, H <: Product, T <: HList, H_deepLGen <: HList, T_hListDeepLGen <: HList](implicit
    headDeepLabelledGeneric: DeepLabelledGeneric.Aux[H, H_deepLGen],
    tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
  ): Aux[FieldType[K, H] :: T, FieldType[K, H_deepLGen] :: T_hListDeepLGen] = instance({
    case h :: t => field[K](headDeepLabelledGeneric.to(h)) :: tailHListDeepLabelledGeneric.to(t)
  }, {
    case h :: t => field[K](headDeepLabelledGeneric.from(h)) :: tailHListDeepLabelledGeneric.from(t)
  })
}


trait DeepMapper[P <: Poly1, In <: HList] extends DepFn1[In] {
  type Out <: HList
}

trait LowPriorityDeepMapper {
  def apply[P <: Poly1, L <: HList](implicit deepMapper: DeepMapper[P, L]): Aux[P, L, deepMapper.Out] = deepMapper
  type Aux[P <: Poly1, In <: HList, Out0 <: HList] = DeepMapper[P, In] {type Out = Out0}
  def instance[P <: Poly1, In <: HList, Out0 <: HList](f: In => Out0): Aux[P, In, Out0] = new DeepMapper[P, In] {
    override type Out = Out0
    override def apply(t: In): Out = f(t)
  }

  implicit def headNotHList[P <: Poly1, H, T <: HList](implicit
    headCase: poly.Case1[P, H],
    tailDeepMapper: DeepMapper[P, T]
  ): Aux[P, H :: T, headCase.Result :: tailDeepMapper.Out] =
    instance(l => headCase(l.head) :: tailDeepMapper(l.tail))
}

object DeepMapper extends LowPriorityDeepMapper {
  implicit def hNil[P <: Poly1]: Aux[P, HNil, HNil] = instance(_ => HNil)

  implicit def headHList[P <: Poly1, K <: Symbol, H <: HList, H_deepMap <: HList, T <: HList](implicit
    headDeepMapper: DeepMapper.Aux[P, H, H_deepMap],
    headCase: poly.Case1[P, FieldType[K, H_deepMap]],
    tailDeepMapper: DeepMapper[P, T]
  ): Aux[P, FieldType[K, H] :: T, headCase.Result :: tailDeepMapper.Out] =
    instance(l => headCase(field[K](headDeepMapper(l.head))) :: tailDeepMapper(l.tail))
}

trait DeepFillWith[P <: Poly0, L <: HList] extends DepFn0 {
  type Out = L
}

trait LowPriorityDeepFillWith {
  def apply[P <: Poly0, L <: HList](implicit deepFillWith: DeepFillWith[P, L]): DeepFillWith[P, L] = deepFillWith
  def instance[P <: Poly0, L <: HList](f: => L): DeepFillWith[P, L] = new DeepFillWith[P, L] {
    override def apply(): L = f
  }

  implicit def headNotHList[P <: Poly0, H, T <: HList](implicit
    headCase: poly.Case0.Aux[P, H],
    tailDeepFillWith: DeepFillWith[P, T]
  ): DeepFillWith[P, H :: T] =
    instance(headCase() :: tailDeepFillWith())
}

object DeepFillWith extends LowPriorityDeepFillWith {
  implicit def hNil[P <: Poly0]: DeepFillWith[P, HNil] = instance(HNil)

  implicit def headHList[P <: Poly1, K <: Symbol, H <: HList, T <: HList](implicit
    headDeepMapper: DeepMapper[P, H],
    tailDeepMapper: DeepMapper[P, T]
  ): Aux[P, FieldType[K, H] :: T, FieldType[K, headDeepMapper.Out] :: tailDeepMapper.Out] =
    instance(l => field[K](headDeepMapper(l.head)) :: tailDeepMapper(l.tail))
}

trait LowPriorityFieldNamesAndTypesPoly extends Poly1 {
  implicit def default[K <: Symbol, V](implicit
    witness: Witness.Aux[K],
    typeable: Typeable[V]
  ): Case.Aux[FieldType[K, V], (String, String)] =
    at(_ => (witness.value.name, typeable.describe))
}

object fieldNamesAndTypesPoly extends LowPriorityFieldNamesAndTypesPoly {
  implicit def cse[K <: Symbol, V <: HList](implicit
    witness: Witness.Aux[K],
  ): Case.Aux[FieldType[K, V], (String, V)] =
    at(v => (witness.value.name, v))
}

object nullPoly extends Poly0 {
  implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}

def classAccessors[T <: Product] = new PartiallyApplied[T]

class PartiallyApplied[T <: Product] {
  def apply[L <: HList]()(implicit
    deepLabelledGeneric: DeepLabelledGeneric.Aux[T, L],
    deepFillWith: DeepFillWith[nullPoly.type, L],
    deepMapper: DeepMapper[fieldNamesAndTypesPoly.type, L],
  ): deepMapper.Out = deepMapper(deepFillWith())
}
classAccessors[MyChgClass]() // (b,Option[String]) :: (c,Option[String]) :: (d,Option[String]) :: HNil
classAccessors[MyFullClass]() // (k,Int) :: (b,String) :: (c,String) :: (d,String) :: HNil
classAccessors[MyEndClass]() // (id,Int) :: (after,(k,Int) :: (b,String) :: (c,String) :: (d,String) :: HNil) :: HNil

Deriving nested shapeless lenses using only a type

Weird behavior trying to convert case classes to heterogeneous lists recursively with Shapeless

https://github.com/milessabin/shapeless/blob/main/examples/src/main/scala/shapeless/examples/deephlister.scala

Converting nested case classes to nested Maps using Shapeless

Automatically convert a case class to an extensible record in shapeless?

  • Related