在Dotty(Scala 3)中,使用宏生成新类是否可行?
Zlaja
在Dotty(Scala 3)中,使用宏生成新类是否可行?
Zlaja
目前在Dotty中只有(某种程度上){{link1:def宏}}。 目前没有(某种程度上的){{link2:宏注释}},可以生成新成员、新类等。
要生成新成员、新类等,您可以使用
让我提醒您,即使在Scalac中生成新成员、新类等功能也不是从一开始就存在的。这样的功能(宏注释)出现在Scalac的Macro Paradise编译器插件中。
我不能排除有人会为Dotty编写类似Macro Paradise的东西。现在为时尚早,因为Dotty目前只有功能冻结,甚至语言语法(例如)和标准库也还在不断变化(还有一些库正在测试它们与Dotty的兼容性,例如目前没有Scalaz/Cats)。
https://github.com/typelevel/simulacrum-scalafix
https://index.scala-lang.org/typelevel/simulacrum-scalafix/simulacrum-scalafix/0.5.0?target=_2.12
一个更多的用例:Breeze使用sbt插件和Scalameta进行源代码生成。https://github.com/scalanlp/breeze
https://github.com/scalanlp/breeze/blob/master/DEVELOP.md
https://github.com/dlwh/sbt-breeze-expand-codegen
更新。 现在我们可以使用Scala 3 (def)宏生成一个内部类:Scala 3宏实现方法重写
更新(2023年3月)。从Scala 3.3.0-RC2开始,出现了宏注释(Nicolas Stucki实现)。
https://github.com/lampepfl/dotty/releases/tag/3.3.0-RC2(讨论)
[概念验证] 通过重写宏注释中的错误生成代码https://github.com/lampepfl/dotty/pull/16545
Zhendong Ang. Scala 3的宏注释(硕士论文)https://infoscience.epfl.ch/record/294615
宏注解(第一部分)https://github.com/lampepfl/dotty/pull/16392
宏注解类修改(第二部分)https://github.com/lampepfl/dotty/pull/16454
允许从MacroAnnotations返回类(第三部分)https://github.com/lampepfl/dotty/pull/16534
新定义在宏展开外部不可见。
build.sbt
scalaVersion := "3.3.0-RC3"
@genObj
生成一个伴生对象,其中包含given tc: TC[A] = new TC[A]
,@modifyObj
修改生成的伴生对象,在内部生成given tc: TC[A] = new TC[A]
:import scala.annotation.{MacroAnnotation, experimental}
import scala.quoted.*
object Macros:
class TC[T]
@experimental
class genObj extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect.*
tree match
case ClassDef(className, _, _, _, _) =>
val modParents = List(TypeTree.of[Object])
val tpe = TypeRepr.of[TC].appliedTo(List(tree.symbol.typeRef))
def decls(cls: Symbol): List[Symbol] = List(
Symbol.newVal(cls, "tc", tpe, Flags.Given, Symbol.noSymbol)
)
val mod = Symbol.newModule(Symbol.spliceOwner, className, Flags.EmptyFlags, Flags.EmptyFlags,
modParents.map(_.tpe), decls, Symbol.noSymbol)
val cls = mod.moduleClass
val tcSym = cls.declaredField("tc")
val tcDef = tpe.asType match
case '[TC[t]] => ValDef(tcSym, Some('{new TC[t]}.asTerm))
val (modValDef, modClsDef) = ClassDef.module(mod, modParents, body = List(tcDef))
val res = List(tree, modValDef, modClsDef)
println(res.map(_.show))
res
case _ =>
report.errorAndAbort("@genObj can annotate only classes")
@experimental
class modifyObj extends MacroAnnotation:
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect.*
tree match
case ClassDef(name, constr, parents, selfOpt, body) =>
val tpe = TypeRepr.of[TC].appliedTo(List(tree.symbol.companionClass.typeRef))
val tcSym = Symbol.newVal(tree.symbol, "tc", tpe, Flags.Given, Symbol.noSymbol)
val tcDef = tpe.asType match
case '[TC[t]] => ValDef(tcSym, Some('{ new TC[t] }.asTerm))
val res = List(ClassDef.copy(tree)(name, constr, parents, selfOpt, body :+ tcDef))
println(res.map(_.show))
res
case _ =>
report.errorAndAbort("@modifyObj can annotate only classes")
import Macros.{TC, genObj, modifyObj}
import scala.annotation.experimental
@experimental
object App:
@genObj
class A
//scalac: List(@Macros.genObj class A(),
//lazy val A: App.A.type = new App.A(),
//object A extends java.lang.Object { this: App.A.type =>
// val tc: Macros.TC[App.A] = new Macros.TC[App.A]()
//})
import Macros.{TC, genObj, modifyObj}
import scala.annotation.experimental
@experimental
object App:
class A
@modifyObj
object A
//scalac: List(@Macros.modifyObj object A {
// val tc: Macros.TC[App.A] = new Macros.TC[App.A]()
//})
您可以制作一个透明宏,返回一个结构类型,用于生成任何您希望的val
和def
类型。
这里是一个示例:当使用Product类型调用方法props
时,将创建一个对象,并将第一个Product元素名称作为String val
。
case class User(firstName: String, age: Int)
// has the type of Props { val firstName: String }
val userProps = props[User]
println(userProps.firstName) // prints "prop for firstName"
println(userProps.lastName) // compile error
实现起来有点棘手,但并不太难:
import scala.compiletime.*
import scala.quoted.*
import scala.deriving.Mirror
class Props extends Selectable:
def selectDynamic(name: String): Any =
"prop for " + name
transparent inline def props[T] =
${ propsImpl[T] }
private def propsImpl[T: Type](using Quotes): Expr[Any] =
import quotes.reflect.*
Expr.summon[Mirror.ProductOf[T]].get match
case '{ $m: Mirror.ProductOf[T] {type MirroredElemLabels = mels; type MirroredElemTypes = mets } } =>
Type.of[mels] match
case '[mel *: melTail] =>
val label = Type.valueOfConstant[mel].get.toString
Refinement(TypeRepr.of[Props], label, TypeRepr.of[String]).asType match
case '[tpe] =>
val res = '{
val p = Props()
p.asInstanceOf[tpe]
}
println(res.show)
res
通过递归,你可以不断地完善Refinement(因为Refinement<:TypeRepr),直到所有内容都构建完成。
话虽如此,使用transparent inline
甚至是Scala 2宏注解生成新类型会使IDE很难支持自动完成。因此,如果可能的话,我建议使用标准Typeclass派生。
你甚至可以推导出标准特质的默认行为:
trait SpringDataRepository[E, Id]:
def findAll(): Seq[E]
trait DerivedSpringDataRepository[E: Mirror.ProductOf, Id]:
def findAll(): Seq[E] = findAllDefault[E, Id]()
private inline def findAllDefault[E, Id](using m: Mirror.ProductOf[E]): Seq[E] =
findAllDefaultImpl[E, m.MirroredLabel, m.MirroredElemLabels]()
private inline def findAllDefaultImpl[E, Ml, Mels](columns: ArrayBuffer[String] = ArrayBuffer()): Seq[E] =
inline erasedValue[Mels] match
case _: EmptyTuple =>
// base case
println("executing.. select " + columns.mkString(", ") + " from " + constValue[Ml])
Seq.empty[E]
case _: (mel *: melTail) =>
findAllDefaultImpl[E, Ml, melTail](columns += constValue[mel].toString)
然后,用户只需要扩展 DerivedSpringDataRepository
与他们的 Product 类型:
case class User(id: Int, first: String, last: String)
class UserRepo extends DerivedSpringDataRepository[User, Int]
val userRepo = UserRepo()
userRepo.findAll() // prints "executing.. select id, first, last from User"
@main
不是宏注解,而是由Dotty编译器管理的单独注解。目前,您无法在Dotty中创建此类自定义注解,除非使用编译器插件。 - Dmytro Mitin