如何为 Scala 3 枚举创建通用方法

How to create a general method for Scala 3 enums

我想为任何 Scala 3 枚举创建一个简单的 enumDescr 函数。

示例:

  @description(enumDescr(InvoiceCategory))
  enum InvoiceCategory:
    case `Travel Expenses`
    case Misc
    case `Software License Costs`

Scala 2 这很简单 (Enumeration):

def enumDescr(enum: Enumeration): String =
  s"$enum: ${enum.values.mkString(", ")}"

但在 Scala 3 中是如何完成的:

def enumDescr(enumeration: ??) = ...

我没有看到所有 enum 伴生对象共有的任何共同特征。

您仍然可以反射式地调用 values,但是:

import reflect.Selectable.reflectiveSelectable

def descrEnum(e: { def values: Array[?] }) = e.values.mkString(",")

enum Foo:
  case Bar
  case Baz

descrEnum(Foo) // "Bar,Baz"

Java 反思

如果您将 enum 声明为 Java 兼容,则可以使用 Java 反射通过 Class.getEnumConstants 方法获取其值的数组。

要声明一个 Java-compatible enum 它必须扩展 Enum class:

enum Color extends Enum[Color]:
  case Red, Green, Blue

您可以在静态 class 上使用 getEnumConstants 来正确输入 Array 个值:

val values: Array[Color] = classOf[Color].getEnumConstants

但是如果你想以通用方式使用它,我相信你必须将 ClassArray 转换为正确的类型 asInstanceOf:

def enumValues[E <: Enum[E] : ClassTag]: Array[E] =
  classTag[E].runtimeClass.getEnumConstants.asInstanceOf[Array[E]]

那里并不严格需要子类型声明 <: Enum[E],但用于避免使用不相关的 classes 调用它并导致运行时异常。

现在一个方法enumDescr可以用类似的方式写成:

def enumDescr[E <: Enum[E] : ClassTag]: String =
  val cl = classTag[E].runtimeClass.asInstanceOf[Class[E]]
  s"${cl.getName}: ${cl.getEnumConstants.mkString(", ")}"

然后这样调用:

scala> enumDescr[Color]
val res0: String = Color: Red, Green, Blue

Scala 编译时反射

如果你只想要枚举案例的名称,你可以使用 scala.deriving.Mirror(感谢@unclebob 的想法):

import scala.deriving.Mirror
import scala.compiletime.{constValue, constValueTuple}

enum Color:
  case Red, Green, Blue

inline def enumDescription[E](using m: Mirror.SumOf[E]): String =
  val name = constValue[m.MirroredLabel]
  val values = constValueTuple[m.MirroredElemLabels].productIterator.mkString(", ")
  s"$name: $values"

@main def run: Unit =
  println(enumDescription[Color])

这会打印:

Color: Red, Green, Blue

值序列的 Scala 3 宏

您可以使用 Scala 3 宏在伴随对象上生成对 values 的调用。

无法调用同一文件中的宏定义,因此宏必须放在单独的文件中:

/* EnumValues.scala */

import scala.quoted.*

inline def enumValues[E]: Array[E] = ${enumValuesImpl[E]}

def enumValuesImpl[E: Type](using Quotes): Expr[Array[E]] =
  import quotes.reflect.*
  val companion = Ref(TypeTree.of[E].symbol.companionModule)
  Select.unique(companion, "values").asExprOf[Array[E]]

然后在主文件中:

enum Color:
  case Red, Green, Blue

// Usable from `inline` methods:
inline def genericMethodTest[E]: String =
  enumValues[E].mkString(", ")

@main def run: Unit =
  println(enumValues[Color].toSeq)
  println(genericMethodTest[Color])

您可以使用包含您需要的所有信息的 Mirror.SumOf[A] 创建内联定义。

https://docs.scala-lang.org/scala3/reference/contextual/derivation.html