如何为 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
但是如果你想以通用方式使用它,我相信你必须将 Class
或 Array
转换为正确的类型 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
我想为任何 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
但是如果你想以通用方式使用它,我相信你必须将 Class
或 Array
转换为正确的类型 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