如何使用宏在 Dotty 中生成 class?
How to generate a class in Dotty with macro?
是否可以在 Dotty、Scala 3 中使用宏生成新的 class?
兹拉亚
目前在Dotty中只有(一种)def macros. Currently there is no (kind of) macro annotations,可以生成新成员,新class等
对于新成员的生成,新 class 等你可以使用
Scalameta (without or with SemanticDB, Scalafix [see also] depending on whether such generation is just syntactic or semantic),它在编译时间(源代码生成)之前的时间工作,或者
a compiler plugin,在编译时有效。
让我提醒你,即使在 Scalac 中生成新成员的能力,新 class 等也不是从一开始就出现的。此类功能(宏注释)作为 Scalac 的 Macro Paradise 编译器插件出现。
我不能排除有人会为 Dotty 编写类似 Macro Paradise 的内容。现在还为时过早,现在只是 Dotty 的功能冻结,甚至是语言语法(for example) and standard library keep changing now (there is also list of libraries 正在测试他们使用 Dotty 的能力,例如目前没有 Scalaz/Cats)。
您可以创建一个 transparent macro that returns a structural type 来生成您想要的任何 val
和 def
类型。
这是一个例子;方法 props
,当使用 Product 类型调用时,创建一个对象,其第一个 Product 元素名称为字符串 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 derivation.
您甚至可以导出标准特征的默认行为:
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
:
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"
是否可以在 Dotty、Scala 3 中使用宏生成新的 class?
兹拉亚
目前在Dotty中只有(一种)def macros. Currently there is no (kind of) macro annotations,可以生成新成员,新class等
对于新成员的生成,新 class 等你可以使用
Scalameta (without or with SemanticDB, Scalafix [see also] depending on whether such generation is just syntactic or semantic),它在编译时间(源代码生成)之前的时间工作,或者
a compiler plugin,在编译时有效。
让我提醒你,即使在 Scalac 中生成新成员的能力,新 class 等也不是从一开始就出现的。此类功能(宏注释)作为 Scalac 的 Macro Paradise 编译器插件出现。
我不能排除有人会为 Dotty 编写类似 Macro Paradise 的内容。现在还为时过早,现在只是 Dotty 的功能冻结,甚至是语言语法(for example) and standard library keep changing now (there is also list of libraries 正在测试他们使用 Dotty 的能力,例如目前没有 Scalaz/Cats)。
您可以创建一个 transparent macro that returns a structural type 来生成您想要的任何 val
和 def
类型。
这是一个例子;方法 props
,当使用 Product 类型调用时,创建一个对象,其第一个 Product 元素名称为字符串 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 derivation.
您甚至可以导出标准特征的默认行为:
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
:
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"