1

This is a follow-up question of:

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

Assuming that I want to write a recursive converter that can convert a product type:

case class Prod (
  a: Int,
  b: String
)

into a Record, but unlike the above question which uses each case class fields (a, b) as keys, I want to use each class name or type/type-constructor name directly. So this product type becomes a Record at compile time:

"Int" ->> Int
"String" ->> String

(probably not a good enough use case, but you got the idea)

One key step of this is to use reflection to get the name of each class at compile time, and convert them into Singleton types or shapeless witness. I wonder if this capability is already provided somewhere? Or do I absolutely need a whitebox macro to make it happen?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
tribbloid
  • 4,026
  • 14
  • 64
  • 103

1 Answers1

2

You'll have to write a macro

import shapeless.ops.hlist.Mapper
import shapeless.{Generic, HList, Poly1, Typeable}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

trait FieldTypes[A <: Product] {
  type Out <: HList
}
object FieldTypes {
  type Aux[A <: Product, Out0 <: HList] = FieldTypes[A] { type Out = Out0 }

  implicit def mkFieldTypes[A <: Product, L <: HList](implicit
    generic: Generic.Aux[A, L],
    mapper: Mapper[typeablePoly.type, L]
  ): Aux[A, mapper.Out] = null

  object typeablePoly extends Poly1 {
    implicit def cse[A](implicit typeable: Typeable[A]): Case[A] = macro cseImpl[A]
    def cseImpl[A: c.WeakTypeTag](c: whitebox.Context)(typeable: c.Tree): c.Tree = {
      import c.universe._
      val str = c.eval(c.Expr[String](c.untypecheck(q"$typeable.describe")))
      val tpA = weakTypeOf[A]
      q"null.asInstanceOf[FieldTypes.typeablePoly.Case.Aux[$tpA, _root_.shapeless.labelled.FieldType[$str, $tpA]]]"
    }
  }
}

Testing:

import shapeless.{HNil, ::}
import shapeless.labelled.FieldType

implicitly[FieldTypes.Aux[Prod, FieldType["Int", Int] :: FieldType["String", String] :: HNil]]
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • wow, OK. I'll seek to merge your code into singleton-ops (with all due credit addressed obviously). But let's hang it for a few weeks to see if a dotty-friendly answer could appear – tribbloid Mar 28 '21 at 16:11
  • Oh one more thing. The answer may have a missing piece, when you assign `implicitly[...` to a value it will cause a compilation error: `could not find implicit value for parameter e: org.shapesafe.m.FieldTypes.Aux[` – tribbloid Mar 29 '21 at 22:58
  • @tribbloid Well, it's weird if `implicitly[X]` compiles while `val x = implicitly[X]` doesn't. Don't you forget that `FieldTypes` and `implicitly...` must be in different subprojects (as always for macros in Scala 2)? I can't prepare a snippet at Scastie because it doesn't support multiple subprojects. I can publish at Github. – Dmytro Mitin Mar 30 '21 at 10:45
  • yeah, it's weird (https://github.com/tribbloid/shapesafe/runs/2262732200?check_suite_focus=true) Probably a compiler bug. Bloop/BSP however will compile it fine – tribbloid Apr 04 '21 at 04:24