【问题标题】:How to generate a class in Dotty with macro?如何使用宏在 Dotty 中生成一个类?
【发布时间】:2019-12-24 21:26:21
【问题描述】:

是否可以在 Dotty、Scala 3 中生成带有宏的新类?

兹拉亚

【问题讨论】:

  • 宏从来都不是用来创建全新的类型(包括类)的。充其量你可以创建一个针对某些参数调整的类的宏生成实例。我认为宏注释和/或编译器插件可用于将新的类主体插入到带注释的类的伴随对象中(不是一个好主意),但就是这样。如果您想生成类,请改用代码生成器。
  • 对于 Dotty 中的 @main 注释,我只在源代码中找到 class: main extends scala.annotation.Annotation {}。那么,宏注解是否需要编译器插件才能工作?
  • @zlaja @main 不是宏注解,它是由 Dotty 编译器管理的单个注解。目前,如果没有 Dotty 的编译器插件,您将无法创建此类自定义注释。

标签: scala annotations metaprogramming scala-macros dotty


【解决方案1】:

目前在 Dotty 中只有(某种)def macros。目前没有(某种)macro annotations,它可以生成新成员、新类等。

对于生成新成员、新类等,您可以使用

让我提醒您,即使在 Scalac 中,生成新成员、新类等的能力也不是从一开始就出现的。这样的功能(宏注解)以Macro Paradise Scalac 的编译器插件的形式出现。

我不能排除某些时候有人会为 Dotty 写诸如 Macro Paradise 之类的东西。现在还为时过早,现在只是 Dotty 的功能冻结,甚至语言语法 (for example) 和标准库现在也在不断变化(还有 list of libraries 正在测试他们与 Dotty 一起工作的能力,例如目前没有Scalaz/Cats 在那里)。

【解决方案2】:

您可以创建一个transparent 宏,该宏返回一个structural type,以生成您希望的任何vals 和defs 的类型。

这是一个例子;方法 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"

【讨论】:

    猜你喜欢
    • 2014-07-06
    • 1970-01-01
    • 2020-11-01
    • 2020-04-14
    • 2011-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多