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

pme*_*pme 9 enums scala scala-3

我想要enumDescr为任何 Scala 3 枚举提供一个简单的函数。

例子:

  @description(enumDescr(InvoiceCategory))
  enum InvoiceCategory:
    case `Travel Expenses`
    case Misc
    case `Software License Costs`
Run Code Online (Sandbox Code Playgroud)

Scala 2中这很简单 ( Enumeration):

def enumDescr(enum: Enumeration): String =
  s"$enum: ${enum.values.mkString(", ")}"
Run Code Online (Sandbox Code Playgroud)

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

def enumDescr(enumeration: ??) = ...
Run Code Online (Sandbox Code Playgroud)

Kol*_*mar 11

Java反射

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

要声明Java 兼容,enum它必须扩展该类Enum

enum Color extends Enum[Color]:
  case Red, Green, Blue
Run Code Online (Sandbox Code Playgroud)

getEnumConstants您可以在静态类上使用来获取正确类型Array的值:

val values: Array[Color] = classOf[Color].getEnumConstants
Run Code Online (Sandbox Code Playgroud)

但如果您想以通用方式使用它,我相信您必须使用以下命令将 theClass或 the转换Array为正确的类型asInstanceOf

def enumValues[E <: Enum[E] : ClassTag]: Array[E] =
  classTag[E].runtimeClass.getEnumConstants.asInstanceOf[Array[E]]
Run Code Online (Sandbox Code Playgroud)

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

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

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

并这样调用:

scala> enumDescr[Color]
val res0: String = Color: Red, Green, Blue
Run Code Online (Sandbox Code Playgroud)

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])
Run Code Online (Sandbox Code Playgroud)

这打印:

Color: Red, Green, Blue
Run Code Online (Sandbox Code Playgroud)

用于值序列的 Scala 3 宏

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

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

/* 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]]
Run Code Online (Sandbox Code Playgroud)

然后在主文件中:

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])
Run Code Online (Sandbox Code Playgroud)


And*_*kin 4

我没有看到所有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"
Run Code Online (Sandbox Code Playgroud)

  • 似乎需要改进 (4认同)