Scala:如何在case类构造函数中使用类型作为第一类值?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Scala:如何在case类构造函数中使用类型作为第一类值?相关的知识,希望对你有一定的参考价值。
假设我有几个自动生成的类,如MyEnum1
,MyEnum2
,...(它们不一定是Scala枚举类型,只是一些自动生成的类)。虽然MyEnum1
的类型不同于MyEnum2
的类型(并且它们除了Any
之外它们不共享自动生成的父类型),但我可以保证所有这些自动生成的类型具有完全相同的公共,静态方法,特别是findById
和findByName
,它允许根据索引或字符串名称查找枚举值。
我正在尝试创建一个函数,它将使用findById
和findByName
的类型特定版本,但是通用接受任何MyEnum1
,MyEnum2
,...作为函数参数。
请注意,从不同的枚举中创建一个sum类型的典型sealed trait
+ case class
模式在这里没有用,因为我在谈论基于类型参数调度不同的静态方法,并且根本不涉及任何实际的值参数。
例如,假设MyEnum1
编码男/女性别。所以MyEnum1.findById(0)
返回MyEnum1.Female
类型MyEnum1
。并且说MyEnum2
编码眼睛颜色,因此MyEnum2.findById(0)
返回MyEnum2.Green
类型MyEnum2
。
我得到一个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
样板上挣扎了一段时间,它似乎在概念上并不正确。无论我将MyEnum1
或MyEnum2
的值包装到像FromMyEnum1(e: MyEnum1)
这样的case类值构造函数中并尝试定义操作该值的implicits,当我想要执行elemType.findById(...)
时,它在上面的代码示例中没有帮助,因为编译器仍然说那种类型Any
(它解决了我的Map
中的关键类型),没有方法findById
。
我非常希望不将类型本身包装在一个案例类模式中作为键,但我可以这样做 - 除了我无法看到如何将类型本身视为案例类中的第一类值构造函数,天真的样子
case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait
(这样Map
键可以有类型EnumTrait
,并且可能有一些隐含的,将每个case类构造函数与findById
或findByName
的正确实现相匹配)。
任何有助于理解Scala如何使用类型本身作为case类值构造函数内部值的帮助将不胜感激!
你的问题有一些根本的误解。
首先,Scala中没有“静态方法”,所有方法都附加到类的实例。如果您想要一个类的每个实例都相同的方法,您可以将方法添加到该类的伴随对象并在该对象上调用它。
其次,你不能在一个类型上调用一个方法,你只能在一个类型的实例上调用一个方法。所以你不能在你的一个findById
类型上调用MyEnum
,你只能在其中一种类型的实例上调用它。
第三,您不能从方法返回类型,您只能返回类型的实例。
很难确切地说出你想要实现的目标,但我怀疑MyEnum1
,MyEnum2
应该是对象,而不是类。这些继承自您定义的通用接口(findById
,findByName
)。然后,您可以从常用类型的实例创建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包含都存在问题。
但是,你可以做的是这样的:
- 定义一组共同的操作,例如
trait Enum[A] { def findById(id: Int): A = ??? def findByName(name: String): A = ??? def values(): Array[A] = ??? }
- 分离处理每个案例:
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 ...
- 创建一个伴随对象,并使用implicit处理这些案例:
object Enum { def apply[E](implicit e: Enum[E]): Enum[E] = e implicit def buildJavaEnumInstance[E <: java.util.Enum: ClassTag] = ??? implicit def buildCoproductEnum = ??? ... }
- 使用
Enum
作为类型类或其他东西。def iNeedSomeEnumHere[E: Enum](param: String): E = Enum[E].findByName(param)
我同意,这需要大量的前期编码。对于图书馆来说可能是一个好主意,因为我相信不仅是你有这个问题。
以上是关于Scala:如何在case类构造函数中使用类型作为第一类值?的主要内容,如果未能解决你的问题,请参考以下文章
如何创建类型化工厂方法构造函数的类层次结构并使用抽象类型从 Scala 访问它们?