Scala:如何在 case 类构造函数中使用类型作为一等值?



假设我有几个自动生成的类,如MyEnum1MyEnum2、...(它们不一定是 Scala 枚举类型,只是一些自动生成的类(。虽然MyEnum1的类型与MyEnum2的类型不同(除了Any之外,它们不共享自动生成的父类型(,但我可以保证所有这些自动生成的类型都具有完全相同的公共静态方法可用,特别是findByIdfindByName,它们允许根据索引或字符串名称查找枚举值。

我正在尝试创建一个函数,该函数将利用特定于类型的findByIdfindByName版本,但通用于接受任何MyEnum1MyEnum2、...作为函数参数。

请注意,从不同的枚举中创建 sum 类型的典型sealed trait+case class模式在这里无济于事,因为我谈论的是基于类型参数调度不同的静态方法,并且根本不涉及任何实际值参数。

例如,假设MyEnum1编码男性/女性性别。这样MyEnum1.findById(0)返回类型为MyEnum1MyEnum1.Female。并说MyEnum2编码眼睛颜色,以便MyEnum2.findById(0)返回类型为MyEnum2MyEnum2.Green

我得到了一个 Map,其中键是类型,值是要查找的索引,例如

val typeMap = Map(
MyEnum1 -> 0,
MyEnum2 -> 0
)

我想一般地这样做:

for ( (elemType, idx) <- typeMap ) yield elemType.findById(v)
|---------------|
the goal is to
avoid boilerplate
of defining this
with different
pattern matching
for every enum.

并返回一些看起来像Any的序列类型(可以有元素类型

MyEnum1.Female, MyEnum2.Green, ...

我已经在sealed trait+case class样板中挣扎了一段时间,这在概念上似乎不是正确的方式。无论我是否将MyEnum1MyEnum2的值包装到 case 类值构造函数(如FromMyEnum1(e: MyEnum1)中并尝试定义对该进行操作的隐式,当我想做elemType.findById(...)时,它在上面的代码示例中都没有帮助,因为编译器仍然说该类型Any(它在我的Map中解析了键类型(, 没有方法findById.

我强烈希望不要将类型本身包装在案例类模式中以用作键,但我可以这样做——除了我看不出如何将类型本身视为案例类构造函数中的第一类值,天真地像

case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait

(以便Map键可以具有类型EnumTrait,并且可能有一些隐式将每个 case 类构造函数与findByIdfindByName的正确实现相匹配(。

任何有助于理解 Scala 如何使用类型本身作为 case 类值构造函数中的值将不胜感激!

你的问题中有一些基本的误解。

首先,Scala 中没有"静态方法",所有方法都附加到类的一个实例。如果需要一个对类的每个实例都相同的方法,请将方法添加到该类的伴随对象,并在该对象上调用它。

其次,不能对类型调用方法,只能在类型的实例上调用方法。因此,不能在MyEnum类型之一上调用findById,只能在其中一种类型的实例上调用它。

第三,不能从方法返回类型,只能返回类型的实例。


很难确切地说出您要实现的目标,但我怀疑MyEnum1MyEnum2应该是对象,而不是类。它们继承自您定义的公共接口(findByIdfindByName(。然后,可以将公用类型的实例创建Map到要在findById调用中使用的索引。


示例代码:

trait MyEnum {
def findById(id: Int): Any
def findByName(name: String): Any
}
object MyEnum1 extends MyEnum {
trait Gender
object Male extends Gender
object Female extends Gender
def findById(id: Int): Gender = Male
def findByName(name: String): Gender = Female
}
object MyEnum2 extends MyEnum {
trait Colour
object Red extends Colour
object Green extends Colour
object Blue extends Colour
def findById(id: Int): Colour = Red
def findByName(name: String): Colour = Blue
}
val typeMap = Map(
MyEnum1 -> 0,
MyEnum2 -> 0,
)

for ((elemType, idx) <- typeMap ) yield elemType.findById(idx)

如果无法提供通用父trait,请使用结构类型:

object MyEnum1 {
trait Gender
object Male extends Gender
object Female extends Gender
def findById(id: Int): Gender = Male
def findByName(name: String): Gender = Female
}
object MyEnum2 {
trait Colour
object Red extends Colour
object Green extends Colour
object Blue extends Colour
def findById(id: Int): Colour = Red
def findByName(name: String): Colour = Blue
}
type MyEnum = {
def findById(id: Int): Any
def findByName(name: String): Any
}
val typeMap = Map[MyEnum, Int](
MyEnum1 -> 0,
MyEnum2 -> 0,
)
for ((elemType, idx) <- typeMap) yield elemType.findById(idx)

如果类有一个实际的实例(单例对象计数(,则可以使用结构类型:

type Enum[A] = {
def findById(id: Int): E
def findByName(name: String): E
def values(): Array[E]
}
trait SomeEnum
object SomeEnum {
case object Value1 extends SomeEnum
case object Value2 extends SomeEnum
def findById(id: Int): SomeEnum = ???
def findByName(name: String): SomeEnum = ???
def values(): Array[SomeEnum] = ???
}
trait SomeEnum2
object SomeEnum2 {
case object Value1 extends SomeEnum2
case object Value2 extends SomeEnum2
def findById(id: Int): SomeEnum2 = ???
def findByName(name: String): SomeEnum2 = ???
def values(): Array[SomeEnum2] = ???
}
val x: Enum[SomeEnum] = SomeEnum
val y: Enum[SomeEnum2] = SomeEnum2

因此,如果你只使用Scala,事情就很简单了。

但是 Java 类没有配套对象 - 你最终会得到object mypackage.MyEnum is not a value.这是行不通的。您必须为此使用反射,因此在所有情况下保持 API 一致性时会遇到问题。

但是,您可以做的是这样的:

  1. 定义一组通用操作,例如

    trait Enum[A] {
    def findById(id: Int): A = ???
    def findByName(name: String): A = ???
    def values(): Array[A] = ???
    }
    
  2. 单独处理每种情况:

    def buildJavaEnumInstance[E <: java.util.Enum: ClassTag]: Enum[E] = new Enum[E] {
    // use reflection here to implement methods
    // you dont
    }
    def buildCoproductEnum = // use shapeless or macros to get all known instances
    // https://stackoverflow.com/questions/12078366/can-i-get-a-compile-time-list-of-all-of-the-case-objects-which-derive-from-a-sea
    ...
    
  3. 创建一个伴随对象,并使用隐式处理这些情况:

    object Enum {
    def apply[E](implicit e: Enum[E]): Enum[E] = e
    implicit def buildJavaEnumInstance[E <: java.util.Enum: ClassTag] = ???
    implicit def buildCoproductEnum = ???
    ...
    }
    
  4. 使用Enum作为类型类或其他东西。

    def iNeedSomeEnumHere[E: Enum](param: String): E =
    Enum[E].findByName(param)
    

不过我同意,这需要大量的前期编码。对于图书馆来说可能是一个好主意,因为我相信不仅仅是您有这个问题。

相关内容

  • 没有找到相关文章

最新更新