类型 Class 推导访问默认值
Type Class Derivation accessing default values
在使用 Mirrors 在 Scala 3 中执行类型 class 派生时,是否有一种干净的方法来访问 case class 字段的默认值?例如:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels
将设置为 ("s", "i", "d")
。有没有类似的东西:(Some["bar"], None, Some[3.141592653589793])
?
如果不能,可以使用宏来实现吗?我可以同时使用镜像和宏来派生类型 class 实例吗?
您必须编写一个宏来处理名为 <init>$default
、<init>$default
、... 伴随对象中的方法
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
镜像和宏可以一起工作:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
用法:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
正如 Dmytro 所暗示的,信息是在 class 伴随对象的方法 <init>default$x
中携带的。
但是,Quotes
不鼓励在宏中访问符号的树:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628。
符号树丢失,除非程序用 -Yretain-trees
)
编译
最好让宏求值 <init>default$x
,而不是复制其定义的右侧。
可以将 terms
表示为:
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)
在使用 Mirrors 在 Scala 3 中执行类型 class 派生时,是否有一种干净的方法来访问 case class 字段的默认值?例如:
case class Foo(s: String = "bar", i: Int, d: Double = Math.PI)
Mirror.Product.MirroredElemLabels
将设置为 ("s", "i", "d")
。有没有类似的东西:(Some["bar"], None, Some[3.141592653589793])
?
如果不能,可以使用宏来实现吗?我可以同时使用镜像和宏来派生类型 class 实例吗?
您必须编写一个宏来处理名为 <init>$default
、<init>$default
、... 伴随对象中的方法
import scala.quoted.*
inline def printDefaults[T]: Unit = ${printDefaultsImpl[T]}
def printDefaultsImpl[T](using Quotes, Type[T]): Expr[Unit] = {
import quotes.reflect.*
(1 to 3).map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
).foreach(println)
'{()}
}
printDefaults[Foo]
//Some(Literal(Constant(bar)))
//None
//Some(Select(Ident(Math),PI))
镜像和宏可以一起工作:
import scala.quoted.*
import scala.deriving.*
trait Default[T] {
type Out <: Tuple
def defaults: Out
}
object Default {
transparent inline given mkDefault[T](using
m: Mirror.ProductOf[T],
s: ValueOf[Tuple.Size[m.MirroredElemTypes]]
): Default[T] =
new Default[T] {
type Out = Tuple.Map[m.MirroredElemTypes, Option]
def defaults = getDefaults[T](s.value).asInstanceOf[Out]
}
inline def getDefaults[T](inline s: Int): Tuple = ${getDefaultsImpl[T]('s)}
def getDefaultsImpl[T](s: Expr[Int])(using Quotes, Type[T]): Expr[Tuple] = {
import quotes.reflect.*
val n = s.asTerm.underlying.asInstanceOf[Literal].constant.value.asInstanceOf[Int]
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.flatMap(_.tree.asInstanceOf[DefDef].rhs)
)
def exprOfOption[T](oet: Option[Expr[T]])(using Type[T], Quotes): Expr[Option[T]] = oet match {
case None => Expr(None)
case Some(et) => '{Some($et)}
}
val exprs: List[Option[Expr[Any]]] = terms.map(_.map(_.asExprOf[Any]))
val exprs1: List[Expr[Option[Any]]] = exprs.map(exprOfOption)
Expr.ofTupleFromSeq(exprs1)
}
}
用法:
val d = summon[Default[Foo]]
summon[d.Out =:= (Option[String], Option[Int], Option[Double])] // compiles
d.defaults // (Some(bar),None,Some(3.141592653589793))
正如 Dmytro 所暗示的,信息是在 class 伴随对象的方法 <init>default$x
中携带的。
但是,Quotes
不鼓励在宏中访问符号的树:
https://github.com/lampepfl/dotty/blob/main/library/src/scala/quoted/Quotes.scala#L3628。
符号树丢失,除非程序用 -Yretain-trees
)
最好让宏求值 <init>default$x
,而不是复制其定义的右侧。
可以将 terms
表示为:
val terms: List[Option[Term]] =
(1 to n).toList.map(i =>
TypeRepr.of[T].typeSymbol
.companionClass
.declaredMethod(s"$$lessinit$$greater$$default$$$i")
.headOption
.map(Select(Ref(TypeRepr.of[T].typeSymbol.companionModule),_))
)