英文:
Scala case classes and recursive reflection
问题
给定 2 个 Scala case 类
case class Bar(x: Int)
case class Foo(b: Bar, z: Double)
我有一段代码,它使用反射打印 `Foo` 字段的类型:
import scala.reflect.runtime.universe._
def f[T: TypeTag] = typeOf[T].members.filter(!_.isMethod)
我可以这样调用它:`f[Foo]` 和 `f[Bar]`。调用前者返回一个 `List[Type]`,内容为 `[Bar, Double]`。
如何在列表的第一个元素上调用 `f`?等效地,当 `Foo` 有一个自定义类 `Bar` 时,如何递归地打印类型?等效地,如何从 `Bar` 作为 `Type` 获取 `Bar.type`?
非常感谢
英文:
Given 2 Scala case classes
case class Bar(x: Int)
case class Foo(b: Bar, z: Double)
I have a piece of code that prints the types of Foo
fields using reflection:
import scala.reflect.runtime.universe._
def f[T: TypeTag] = typeOf[T].members.filter(!_.isMethod)
and I call it like f[Foo]
and f[Bar]
. Calling the former returns a List[Type]
as [Bar, Double]
.
How can I call f
on the first element of the list? Equivalently, how can I print types recursively when Foo
has a custom class Bar
? Equivalently how can I get from Bar
as Type
a Bar.type
?
Many thanks
答案1
得分: 3
以下是您要翻译的内容:
你不需要在 `f` 函数中实际使用类型变量 `T`。你可以像这样定义它(正如Dima在评论中建议的那样):
def f(t: Type) =
t.members.filter(!_.isMethod).map(_.typeSignature)
要使用这个来递归打印一个类型:
def printTypesRecursive(t: Type, prefix: String = ""): Unit = {
println(prefix + t)
f(t).foreach(printTypesRecursive(_, prefix + " "))
}
printTypesRecursive(typeOf[Foo])
输出:
Foo
Double
Bar
Int
<details>
<summary>英文:</summary>
You don't actually need the type variable `T` in `f`. You can define it like this (as Dima suggested in the comments):
def f(t: Type) =
t.members.filter(!.isMethod).map(.typeSignature)
To use this to recursively print a type:
def printTypesRecursive(t: Type, prefix: String = ""): Unit = {
println(prefix + t)
f(t).foreach(printTypesRecursive(_, prefix + " "))
}
printTypesRecursive(typeOf[Foo])
Output:
```text
Foo
Double
Bar
Int
答案2
得分: 1
Equivalently how can I get from Bar
as Type
a Bar.type
?
Bar.type
is the type of companion object
https://stackoverflow.com/questions/75286750/class-companion-object-vs-case-class-itself/
I need something like f[f[Foo].head]
I guess you have here some confusion between compile-time and runtime
https://stackoverflow.com/questions/846103/runtime-vs-compile-time
You can call
def f[T: TypeTag] = typeOf[T].members.filter(!_.isMethod)
f[Foo]
//Scope{
// private[this] val z: <?>;
// private[this] val b: <?>
//}
if you know type T
statically i.e. at compile time (earlier).
You can call
def f_dyn(tpe: Type) = tpe.members.filter(!_.isMethod)
f_dyn(typeOf[Foo])
//Scope{
// private[this] val z: <?>;
// private[this] val b: <?>
//}
if you know type tpe
dynamically i.e. at runtime (later).
You can express f
via f_dyn
def f[T: TypeTag] = f_dyn(typeOf[T])
def f_dyn(tpe: Type) = tpe.members.filter(!_.isMethod)
If you want to iterate the method (apply it recursively) then it should return something like it accepts, i.e. now this is types rather than symbols, so you need to add somewhere something like .typeSignature
, .asMethod.returnType
, .asType.toType
. Also maybe now you're more interested in .decls
rather than .members
since you are not looking for inherited members. Also .decls
returns field symbols in correct order on contrary to .members
. Finally let it be better List[...]
rather than raw Scope
(.toList
)
def f[T: TypeTag]: List[Type] = f_dyn(typeOf[T])
def f_dyn(tpe: Type): List[Type] =
tpe.decls.filter(!_.isMethod).map(_.typeSignature).toList
f_dyn(f[Foo].head) // List(Int)
f_dyn(f_dyn(typeOf[Foo]).head) // List(Int)
You can iterate f_dyn
f_dyn(typeOf[Foo]) // List(Bar, Double)
f_dyn(typeOf[Foo]).map(f_dyn) // List(List(Int), List())
f_dyn(typeOf[Foo]).map(f_dyn).map(_.map(f_dyn)) // List(List(List()), List())
If you really want to iterate f
rather than f_dyn
then the complication is that you can call f[T]
for the second time only on a statically known type T
but you have the type that is the result of the first call only at runtime, you don't have it at compile time. In principle you can use runtime compilation (creating new compile time inside runtime) although this can work slower than ordinary reflection and doesn't seem needed now
import scala.reflect.runtime.{currentMirror => rm}
import scala.tools.reflect.ToolBox // libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
val tb = rm.mkToolBox()
// suppose f is defined in object App
tb.eval(q"App.f[${f[Foo].head}]") // List(Int)
tb.eval(q"""
import App._
f[${f[Foo].head}]
""")
// List(Int)
Now all the classes Foo
, Bar
... are defined at compile time so it would make sense to use compile-time reflection (macros) rather than runtime reflection
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def f[T]: List[String] = macro Macros.f_impl[T]
def f1[T]: List[List[String]] = macro Macros.f1_impl[T]
def f2[T]: List[List[List[String]]] = macro Macros.f2_impl[T]
class Macros(val c: blackbox.Context) {
import c.universe._
def f_dyn(tpe: Type): List[Type] =
tpe.decls.filter(!_.isMethod).map(_.typeSignature).toList
val ListObj = q"_root_.scala.List"
val ListT = tq"_root_.scala.List"
val StringT = tq"_root_.scala.Predef.String"
def f_impl[T: WeakTypeTag]: Tree = {
val types: List[Type] = f_dyn(weakTypeOf[T])
val typeStrings: List[String] = types.map(_.toString)
q"$ListObj.apply[$StringT](..$typeStrings)"
}
def f1_impl[T: WeakTypeTag]: Tree = {
val types: List[List[Type]] = f_dyn(weakTypeOf[T]).map(f_dyn)
val typeStrings: List[List[String]] = types.map(_.map(_.toString))
q"$ListObj.apply[$ListT[$StringT]](..$typeStrings)"
}
def f2_impl[T: WeakTypeTag]: Tree = {
val types: List[List[List[Type]]] =
f_dyn(weakTypeOf[T]).map(f_dyn).map(_.map(f_dyn))
val typeStrings: List[List[List[String]]] = types.map(_.map(_.map(_.toString)))
q"$ListObj.apply[$ListT[$ListT[$StringT]]](..$typeStrings)"
}
}
// in a different subproject
f[Foo]
//scalac: _root_.scala.List.apply[_root_.scala.Predef.String]("Bar", "Double")
f1[Foo]
//scalac: _root_.scala.List.apply[_root_.scala.List[_root_.scala.Predef.String]](scala.collection.immutable.List("Int"), scala.collection.immutable.List())
f2[Foo]
//scalac: _root_.scala.List.apply[_root_.scala.List[_root_.scala.List[_root_.scala.Predef.String]]](scala.collection.immutable.List(scala.collection.immutable.List()), scala.collection.immutable.List())
The runtime of macros (when they are expanded) is the compile time of main code.
Do macros support annotations too? like can I access my case class annotations with macros? with runtime reflection , i would do symbolOf[Foo].asClass.annotations
Yes, surely.
def foo[T]: Unit = macro fooImpl[T]
def fooImpl[T: c.WeakTypeTag
<details>
<summary>英文:</summary>
> Equivalently how can I get from `Bar` as `Type` a `Bar.type`?
`Bar.type` is the type of companion object
https://stackoverflow.com/questions/75286750/class-companion-object-vs-case-class-itself/
> I need something like `f[f[Foo].head]`
I guess you have here some confusion between compile-time and runtime
https://stackoverflow.com/questions/846103/runtime-vs-compile-time
You can call
def f[T: TypeTag] = typeOf[T].members.filter(!_.isMethod)
f[Foo]
//Scope{
// private[this] val z: <?>;
// private[this] val b: <?>
//}
if you know type `T` statically i.e. at compile time (earlier).
You can call
def f_dyn(tpe: Type) = tpe.members.filter(!_.isMethod)
f_dyn(typeOf[Foo])
//Scope{
// private[this] val z: <?>;
// private[this] val b: <?>
//}
if you know type `tpe` dynamically i.e. at runtime (later).
You can express `f` via `f_dyn`
def f[T: TypeTag] = f_dyn(typeOf[T])
def f_dyn(tpe: Type) = tpe.members.filter(!_.isMethod)
If you want to iterate the method (apply it recursively) then it should return something like it accepts, i.e. now this is types rather than symbols, so you need to add somewhere something like `.typeSignature`, `.asMethod.returnType`, `.asType.toType`. Also maybe now you're more interested in `.decls` rather than `.members` since you are not looking for inherited members. Also `.decls` returns field symbols in correct order on contrary to `.members`. Finally let it be better `List[...]` rather than raw `Scope` (`.toList`)
def f[T: TypeTag]: List[Type] = f_dyn(typeOf[T])
def f_dyn(tpe: Type): List[Type] =
tpe.decls.filter(!.isMethod).map(.typeSignature).toList
f_dyn(f[Foo].head) // List(Int)
f_dyn(f_dyn(typeOf[Foo]).head) // List(Int)
You can iterate `f_dyn`
f_dyn(typeOf[Foo]) // List(Bar, Double)
f_dyn(typeOf[Foo]).map(f_dyn) // List(List(Int), List())
f_dyn(typeOf[Foo]).map(f_dyn).map(_.map(f_dyn)) // List(List(List()), List())
If you really want to iterate `f` rather than `f_dyn` then the complication is that you can call `f[T]` for the second time only on a statically known type `T` but you have the type that is the result of the first call only at runtime, you don't have it at compile time. In principle you can use [runtime compilation](https://docs.scala-lang.org/overviews/reflection/symbols-trees-types.html#tree-creation-via-parse-on-toolboxes) (creating new compile time inside runtime) although this can work slower than ordinary reflection and doesn't seem needed now
import scala.reflect.runtime.{currentMirror => rm}
import scala.tools.reflect.ToolBox // libraryDependencies += scalaOrganization.value % "scala-compiler" % scalaVersion.value
val tb = rm.mkToolBox()
// suppose f is defined in object App
tb.eval(q"App.f[${f[Foo].head}]") // List(Int)
tb.eval(q"""
import App._
f[${f[Foo].head}]
""")
// List(Int)
Now all the classes `Foo`, `Bar`... are defined at compile time so it would make sense to use compile-time reflection ([macros](https://docs.scala-lang.org/overviews/macros/overview.html)) rather than [runtime reflection](https://docs.scala-lang.org/overviews/reflection/overview.html)
https://stackoverflow.com/questions/75286557/getting-case-class-definition-which-points-to-another-case-class
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def f[T]: List[String] = macro Macros.f_impl[T]
def f1[T]: List[List[String]] = macro Macros.f1_impl[T]
def f2[T]: List[List[List[String]]] = macro Macros.f2_impl[T]
class Macros(val c: blackbox.Context) {
import c.universe._
def f_dyn(tpe: Type): List[Type] =
tpe.decls.filter(!.isMethod).map(.typeSignature).toList
val ListObj = q"root.scala.List"
val ListT = tq"root.scala.List"
val StringT = tq"root.scala.Predef.String"
def f_impl[T: WeakTypeTag]: Tree = {
val types: List[Type] = f_dyn(weakTypeOf[T])
val typeStrings: List[String] = types.map(_.toString)
q"$ListObj.apply$StringT"
}
def f1_impl[T: WeakTypeTag]: Tree = {
val types: List[List[Type]] = f_dyn(weakTypeOf[T]).map(f_dyn)
val typeStrings: List[List[String]] = types.map(.map(.toString))
q"$ListObj.apply$ListT[$StringT]"
}
def f2_impl[T: WeakTypeTag]: Tree = {
val types: List[List[List[Type]]] =
f_dyn(weakTypeOf[T]).map(f_dyn).map(.map(f_dyn))
val typeStrings: List[List[List[String]]] = types.map(.map(.map(.toString)))
q"$ListObj.apply$ListT[$ListT[$StringT]]"
}
}
// in a different subproject
f[Foo]
//scalac: root.scala.List.apply[root.scala.Predef.String]("Bar", "Double")
f1[Foo]
//scalac: root.scala.List.apply[root.scala.List[root.scala.Predef.String]](scala.collection.immutable.List("Int"), scala.collection.immutable.List())
f2[Foo]
//scalac: root.scala.List.apply[root.scala.List[root.scala.List[root.scala.Predef.String]]](scala.collection.immutable.List(scala.collection.immutable.List()), scala.collection.immutable.List())
The runtime of macros (when they are expanded) is the compile time of main code.
> Do macros support annotations too? like can I access my case class annotations with macros? with runtime reflection , i would do `symbolOf[Foo].asClass.annotations`
Yes, surely.
def foo[T]: Unit = macro fooImpl[T]
def fooImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
println(symbolOf[T].asClass.annotations)
q"()"
}
class myAnnot extends StaticAnnotation
@myAnnot
case class Foo(b: Bar, z: Double)
symbolOf[Foo].asClass.annotations // at runtime: List(myAnnot)
foo[Foo]
// at compile time with scalacOptions += "-Ymacro-debug-lite":
// scalac: List(myAnnot)
One more option to perform compile-time calculations is to use one of libraries encapsulating work with macros e.g. [Shapeless](https://github.com/milessabin/shapeless)
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.{::, DepFn0, DepFn1, HList, HNil, Generic, Poly0, Poly1, Typeable, poly}
trait DeepGeneric[T <: Product] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
object DeepGeneric {
type Aux[T <: Product, Repr0 <: HList] = DeepGeneric[T] {type Repr = Repr0}
def instance[T <: Product, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepGeneric[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
generic: Generic.Aux[A, L],
hListDeepGeneric: HListDeepGeneric.Aux[L, L1]
): Aux[A, L1] = instance(a => hListDeepGeneric.to(generic.to(a)), l1 => generic.from(hListDeepGeneric.from(l1)))
}
trait HListDeepGeneric[T <: HList] {
type Repr <: HList
def to(t: T): Repr
def from(r: Repr): T
}
trait LowPriorityHListDeepGeneric {
type Aux[T <: HList, Repr0 <: HList] = HListDeepGeneric[T] {type Repr = Repr0}
def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepGeneric[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_hListDeepGen <: HList](implicit
tailHListDeepGeneric: HListDeepGeneric.Aux[T, T_hListDeepGen]
): Aux[H :: T, H :: T_hListDeepGen] = instance({
case h :: t => h :: tailHListDeepGeneric.to(t)
}, {
case h :: t => h :: tailHListDeepGeneric.from(t)
})
}
object HListDeepGeneric extends LowPriorityHListDeepGeneric {
implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)
implicit def headCaseClass[H <: Product, T <: HList, H_deepGen <: HList, T_hListDeepGen <: HList](implicit
headDeepGeneric: DeepGeneric.Aux[H, H_deepGen],
tailHListDeepGeneric: HListDeepGeneric.Aux[T, T_hListDeepGen]
): Aux[H :: T, H_deepGen :: T_hListDeepGen] = instance({
case h :: t => headDeepGeneric.to(h) :: tailHListDeepGeneric.to(t)
}, {
case h :: t => headDeepGeneric.from(h) :: tailHListDeepGeneric.from(t)
})
}
trait DeepMapper[P <: Poly1, In <: HList] extends DepFn1[In] {
type Out <: HList
}
trait LowPriorityDeepMapper {
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, H <: HList, H_deepMap <: HList, T <: HList](implicit
// headDeepMapper: DeepMapper.Aux[P, H, H_deepMap],
// headCase: poly.Case1[P, H_deepMap], // apply poly one more time
// tailDeepMapper: DeepMapper[P, T]
// ): Aux[P, H :: T, headCase.Result :: tailDeepMapper.Out] =
// instance(l => headCase(headDeepMapper(l.head)) :: tailDeepMapper(l.tail))
implicit def headHList[P <: Poly1, H <: HList, T <: HList](implicit
headDeepMapper: DeepMapper[P, H], // don't apply poly one more time
tailDeepMapper: DeepMapper[P, T]
): Aux[P, H :: T, headDeepMapper.Out :: tailDeepMapper.Out] =
instance(l => 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 <: Poly0, H <: HList, T <: HList](implicit
headDeepFillWith: DeepFillWith[P, H],
tailDeepFillWith: DeepFillWith[P, T]
): DeepFillWith[P, H :: T] =
instance(headDeepFillWith() :: tailDeepFillWith())
}
// needed if DeepMapper "applies poly one more time",
// e.g. for field NAMES and types (via DeepLabelledGeneric), not just types (via DeepGeneric)
// trait LowPriorityTypeablePoly extends Poly1 {
// implicit def notHListCase[V](implicit typeable: Typeable[V]): Case.Aux[V, String] =
// at(_ => typeable.describe)
// }
//
// object typeablePoly extends LowPriorityTypeablePoly {
// implicit def hListCase[V <: HList]: Case.Aux[V, V] = at(identity)
// }
object typeablePoly extends Poly1 {
implicit def cse[A](implicit typeable: Typeable[A]): Case.Aux[A, String] =
at(_ => typeable.describe)
}
object nullPoly extends Poly0 {
implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}
def classFieldTypes[T <: Product] = new PartiallyApplied[T]
class PartiallyApplied[T <: Product] {
def applyL <: HList(implicit
deepGeneric: DeepGeneric.Aux[T, L],
deepFillWith: DeepFillWith[nullPoly.type, L],
deepMapper: DeepMapper[typeablePoly.type, L],
): deepMapper.Out = deepMapper(deepFillWith())
}
classFieldTypesBar // Int :: HNil
classFieldTypesFoo // (Int :: HNil) :: Double :: HNil
`Generic`/`LabelledGeneric`/`DeepGeneric`, `Mapper`/`DeepMapper`, `FillWith`/`DeepFillWith`, `Typeable` are [type classes](https://stackoverflow.com/questions/5408861/what-are-type-classes-in-scala-useful-for).
> lets say for each Type I want the code to behave differently, if `Double` do x, if `Int` do y.
You can use types comparisons `t =:= typeOf[Double]`, `t <:< typeOf[Double]` if you use runtime/compile-time reflection or you can keep using type classes and polymorphic functions
trait MyTypeclass[T] {
def apply(): Unit
}
object MyTypeclass {
implicit val double: MyTypeclass[Double] = () => println("do x")
implicit val int: MyTypeclass[Int] = () => println("do y")
implicit def caseClass[T <: Product, L <: HList](implicit
deepGeneric: DeepGeneric.Aux[T, L],
deepFillWith: DeepFillWith[nullPoly.type, L],
deepMapper: DeepMapper[myPoly.type, L]
): MyTypeclass[T] = () => deepMapper(deepFillWith())
}
object myPoly extends Poly1 {
implicit def cse[T: MyTypeclass]: Case.Aux[T, Unit] = at(_ => foo)
}
def foo[T](implicit tc: MyTypeclass[T]): Unit = tc()
foo[Int]
// do y
foo[Double]
// do x
foo[Foo]
// do y
// do x
foo[Bar]
// do y
Shapeless is also capable of handling annotations
import shapeless.Annotation
implicitly[Annotation[myAnnot, Foo]].apply() // myAnnot@1a3869f4
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论