Scala 3 中的宏

最佳实践

语言
此文档页面特定于 Scala 3,可能涵盖 Scala 2 中不可用的新概念。 除非另有说明,否则此页面中的所有代码示例都假设您使用的是 Scala 3。

内联

在内联以提高性能时要小心

为了最大程度地利用 JVM JIT 优化,您需要避免生成大型方法。

即将推出

引用代码

保持引用可读

  • 尽量避免使用 ${...},其中包含任意表达式
    • 使用 $someExpr
    • 使用 ${ someExprFrom('localExpr) }

为了说明,请考虑以下示例

val sc: StringContext = ...
'{ StringContext(${Varargs(sc.parts.map(Expr(_)))}: _*) }

相反,我们可以编写以下内容

val sc: StringContext = ...
val partExprs = sc.parts.map(Expr(_))
val partsExpr = Varargs(partExprs)
'{ StringContext($partsExpr: _*) }

在第二个示例中,引用的内容更加清晰。

避免嵌套上下文

请考虑以下代码

val y: Expr[Int] = ...
def body(x: Expr[Int])(using quotes.Nested) =  '{ $x + $y }
'{ (x: Int) => ${ body('x) } }

相反,使用正常的上下文并传递所有需要的表达式。这样做还有一个好处,就是允许函数不在本地定义。

def body(x: Expr[Int], y: Expr[Int])(using Quotes) =
  '{ $x + $y }

val y: Expr[Int] = ...
'{ (x: Int) => ${ body('x, y) } }

引号反映

对于本节,请考虑以下设置

object Box:
  sealed trait Base
  case class Leaf(x: Int) extends Base

// Quotes in contextual scope
val boxTpe : TypeRepr = TypeRepr.of[Box.type]
val baseTpe: TypeRepr = TypeRepr.of[Box.Base]
val baseSym: Symbol   = baseTpe.typeSymbol
val leafTpe: TypeRepr = TypeRepr.of[Box.Leaf]
val leafSym: Symbol   = leafTpe.typeSymbol

避免使用 Symbol.tree

在对象 sym: Symbol 上,sym.tree 返回与符号关联的 Tree。使用此方法时要小心,因为符号的树可能未定义。当与符号关联的代码在与访问不同的时间定义时,如果未使用 -Yretain-trees 编译选项,则符号的 tree 将不可用。源自 Java 代码的符号没有关联的 tree

Symbol 获取 TypeRepr

在前面的标题中,我们看到应该避免使用 Symbol.tree,因此您不应该在 sym: Symbol 上使用 sym.tree.tpe。因此,要获取与 Symbol 对应的 TypeRepr,建议在 tpe: TypeRepr 对象上使用 tpe.memberType

我们可以通过两种方式获得 LeafTypeRepr

  1. TypeRepr.of[Box.Leaf]
  2. boxTpe.memberType(leafSym)(换句话说,我们请求 Box 的成员的 TypeRepr,其符号等于 leafSym 的符号。)

虽然这两种方法是等效的,但第一种方法只有在你已经知道你正在寻找类型 Box.Leaf 时才有可能。第二种方法允许你探索未知的 API。

使用 Symbol 来比较定义

了解更多关于 Symbols 的信息 这里.

Symbols 允许您使用 == 来比较定义

leafSym == baseSym.children.head // Is true

但是,在 TypeRepr 上使用 == 不会产生相同的结果

boxTpe.memberType(baseSym.children.head) == leafTpe // Is false

获取类型的 Symbol

有一个方便的快捷方式来获取 T 定义的 symbol。而不是

TypeTree.of[T].tpe.typeSymbol

您可以使用

TypeRepr.of[T].typeSymbol

使用模式匹配进入 API

模式匹配是 API 的一种非常符合人体工程学的方法。始终查看在 *Module 对象中定义的 unapply 方法。

在您的宏中搜索上下文范围

您可以使用 Implicits.search 搜索给定的实例。

例如

def summonOrFail[T: Type]: Expr[T] =
  val tpe = TypeRepr.of[T]
  Implicits.search(tpe) match
    case success: ImplicitSearchSuccess =>
      val implicitTerm = success.tree
      implicitTerm.asExprOf[T]
    case failure: ImplicitSearchFailure =>
      reflect.report.throwError("Could not find an implicit for " + Type.show[T])

如果您正在编写宏,并且更喜欢处理 ExprExpr.summonImplicits.search 的一个方便的包装器

def summonOrFail[T: Type]: Expr[T] =
  Expr.summon[T] match
    case Some(imp) => imp
    case None => reflect.report.throwError("Could not find an implicit for " + Type.show[T])

此页面的贡献者