【问题标题】:How to create a general method for Scala 3 enums如何为 Scala 3 枚举创建通用方法
【发布时间】:2021-12-13 01:00:37
【问题描述】:

我想为任何 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: ??) = ...

【问题讨论】:

    标签: scala enums scala-3


    【解决方案1】:

    我没有看到所有 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"
    

    【讨论】:

    • 似乎有待改进
    • 编译器正在进行结构更改(添加方法),这些更改未反映在标称类型系统中(没有共同特征),所以,是的...
    【解决方案2】:

    Java 反射

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

    要声明Java-compatible enum,它必须扩展Enum 类:

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

    您可以在静态类上使用getEnumConstants 来正确键入Array 的值:

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

    但如果您想以通用方式使用它,我相信您必须使用asInstanceOfClassArray 转换为正确的类型:

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

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

    现在可以用类似的方式编写方法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])
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 你能分享一个与OP的代码匹配的完整代码示例吗?
      猜你喜欢
      • 1970-01-01
      • 2018-04-18
      • 2021-07-18
      • 1970-01-01
      • 2010-09-09
      相关资源
      最近更新 更多