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

时间:2018-08-31 13:54:08

标签: scala pattern-matching implicit case-class

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

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

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

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

为我提供了一个Map,其键为 type ,值是要查找的索引,例如

MyEnum2.Green

我想一般地这样做:

MyEnum2

并返回一些类似于

的序列类型(可以具有元素类型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 traitcase class values 包装到诸如MyEnum1这样的case类值构造函数中,并尝试定义用于对该 value < / strong>,当我想执行MyEnum2时,在上面的代码示例中无济于事,因为编译器仍会说类型FromMyEnum1(e: MyEnum1)(它将解析为我的{{1 }}),没有方法elemType.findById(...)

我强烈希望不要将类型本身包装在case类模式中以用作键,但是我可以做到这一点-除非我看不到如何将类型本身视为第一类值,否则案例类构造函数,类似天真的

Any

(这样,Map键的类型可能为findById,并且可能存在一些隐式变量,它们使每个案例类构造函数与case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait Map的正确实现相匹配。 )。

任何帮助您了解Scala如何启用使用类型本身作为case类值构造函数内部的值的帮助!

2 个答案:

答案 0 :(得分:1)

您的问题中存在一些基本的误解。

首先,Scala中没有“静态方法”,所有方法都附加到类的实例上。如果您想要一个类的每个实例都相同的方法,则可以向该类的伴随对象添加一个方法,然后在该对象上调用它。

第二,您不能在类型上调用方法,只能在类型的实例上调用方法。因此,您无法在findById类型之一上调用MyEnum,而只能在这些类型之一的实例上调用它。

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


很难准确说明您要实现的目标,但是我怀疑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)

答案 1 :(得分:0)

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

np.add(a, 2, out=b)

因此,如果仅使用Scala,事情就很简单。

但是Java类没有伴随对象-您最终会得到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 。这行不通。为此,您将必须使用反射,因此在所有情况下保持API一致性都存在问题。

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

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

    object mypackage.MyEnum is not a value
  2. 分开处理每个案件:

    trait Enum[A] {
    
      def findById(id: Int): A = ???
      def findByName(name: String): A = ???
      def values(): Array[A] = ???
    }
    
  3. 创建一个伴随对象,并使这些情况用隐式处理:

    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
    
    ...
    
  4. 使用object Enum { def apply[E](implicit e: Enum[E]): Enum[E] = e implicit def buildJavaEnumInstance[E <: java.util.Enum: ClassTag] = ??? implicit def buildCoproductEnum = ??? ... } 作为类型类或其他类型。

    Enum

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