Scala:如何在 case class 构造函数中使用类型作为 first-class 值?
Scala: how to use types as first-class values inside of case class constructors?
假设我有几个自动生成的 classes,比如 MyEnum1
、MyEnum2
、...(它们不一定是 Scala 枚举类型,只是一些自动生成的 classes)。尽管 MyEnum1
的类型与 MyEnum2
的类型不同(并且它们除了 Any
之外没有共享自动生成的父类型),我可以保证所有这些自动生成的类型都有完全相同 public,可用的静态方法,特别是 findById
和 findByName
,允许根据索引或字符串名称查找枚举值。
我正在尝试创建一个函数,该函数将利用 findById
和 findByName
的特定类型版本,但可以接受任何 MyEnum1
、MyEnum2
], ...作为函数参数。
请注意,典型的 sealed trait
+ case class
模式从不同的枚举中创建一个总和类型在这里没有帮助,因为我正在谈论基于类型参数分派不同的静态方法,并且根本不涉及任何实际值参数。
例如,假设 MyEnum1
编码 male/female 性别。因此 MyEnum1.findById(0)
return MyEnum1.Female
类型为 MyEnum1
。并说 MyEnum2
编码眼睛颜色,因此 MyEnum2.findById(0)
returns MyEnum2.Green
类型为 MyEnum2
.
我得到一个 Map,其中键是 type,值是要查找的索引,例如
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
的 values 包装到 case class 值构造函数中,如 FromMyEnum1(e: MyEnum1)
并尝试定义用于操作的隐式在那个 value 上,当我想做 elemType.findById(...)
时,它在我上面的代码示例中没有帮助,因为编译器仍然说类型 Any
(它是什么解析我的 Map
) 中的键类型,没有方法 findById
.
我强烈不希望将类型本身包装在 case class 模式中作为键,但我可以这样做——除了我看不出如何处理类型本身作为 case class 构造函数中的第一个 class 值,天真地类似于
case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait
(这样 Map
键的类型可以是 EnumTrait
并且可能有一些隐式匹配每个案例 class 构造函数到 findById
的正确实现或 findByName
).
如果能帮助理解 Scala 如何将类型本身用作 case class 值构造函数中的值,我们将不胜感激!
你的问题存在一些根本性的误解。
首先,Scala 中没有"static methods",所有方法都附加到class 的实例。如果您想要一个对于 class 的每个实例都相同的方法,您可以向该 class 的伴随对象添加一个方法并在该对象上调用它。
其次,不能调用类型的方法,只能调用类型实例的方法。所以你不能在你的 MyEnum
类型之一上调用 findById
,你只能在这些类型之一的实例上调用它。
第三,你不能return一个方法的类型,你只能return一个类型的实例。
很难准确地说出你想要达到什么目的,但我怀疑 MyEnum1
、MyEnum2
应该是对象,而不是 classes。这些继承自您定义的公共接口 (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)
如果 class 有实际实例(单例对象计数),您可以使用结构类型:
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 classes 没有伴随对象 - 你最终会得到 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
//
...
创建伴生对象并使用隐式处理这些情况:
object Enum {
def apply[E](implicit e: Enum[E]): Enum[E] = e
implicit def buildJavaEnumInstance[E <: java.util.Enum: ClassTag] = ???
implicit def buildCoproductEnum = ???
...
}
使用 Enum
作为类型 class 或其他东西。
def iNeedSomeEnumHere[E: Enum](param: String): E =
Enum[E].findByName(param)
不过我同意,这需要大量的前期编码。对于图书馆来说可能是个好主意,因为我相信不仅仅是你有这个问题。
假设我有几个自动生成的 classes,比如 MyEnum1
、MyEnum2
、...(它们不一定是 Scala 枚举类型,只是一些自动生成的 classes)。尽管 MyEnum1
的类型与 MyEnum2
的类型不同(并且它们除了 Any
之外没有共享自动生成的父类型),我可以保证所有这些自动生成的类型都有完全相同 public,可用的静态方法,特别是 findById
和 findByName
,允许根据索引或字符串名称查找枚举值。
我正在尝试创建一个函数,该函数将利用 findById
和 findByName
的特定类型版本,但可以接受任何 MyEnum1
、MyEnum2
], ...作为函数参数。
请注意,典型的 sealed trait
+ case class
模式从不同的枚举中创建一个总和类型在这里没有帮助,因为我正在谈论基于类型参数分派不同的静态方法,并且根本不涉及任何实际值参数。
例如,假设 MyEnum1
编码 male/female 性别。因此 MyEnum1.findById(0)
return MyEnum1.Female
类型为 MyEnum1
。并说 MyEnum2
编码眼睛颜色,因此 MyEnum2.findById(0)
returns MyEnum2.Green
类型为 MyEnum2
.
我得到一个 Map,其中键是 type,值是要查找的索引,例如
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
的 values 包装到 case class 值构造函数中,如 FromMyEnum1(e: MyEnum1)
并尝试定义用于操作的隐式在那个 value 上,当我想做 elemType.findById(...)
时,它在我上面的代码示例中没有帮助,因为编译器仍然说类型 Any
(它是什么解析我的 Map
) 中的键类型,没有方法 findById
.
我强烈不希望将类型本身包装在 case class 模式中作为键,但我可以这样做——除了我看不出如何处理类型本身作为 case class 构造函数中的第一个 class 值,天真地类似于
case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait
(这样 Map
键的类型可以是 EnumTrait
并且可能有一些隐式匹配每个案例 class 构造函数到 findById
的正确实现或 findByName
).
如果能帮助理解 Scala 如何将类型本身用作 case class 值构造函数中的值,我们将不胜感激!
你的问题存在一些根本性的误解。
首先,Scala 中没有"static methods",所有方法都附加到class 的实例。如果您想要一个对于 class 的每个实例都相同的方法,您可以向该 class 的伴随对象添加一个方法并在该对象上调用它。
其次,不能调用类型的方法,只能调用类型实例的方法。所以你不能在你的 MyEnum
类型之一上调用 findById
,你只能在这些类型之一的实例上调用它。
第三,你不能return一个方法的类型,你只能return一个类型的实例。
很难准确地说出你想要达到什么目的,但我怀疑 MyEnum1
、MyEnum2
应该是对象,而不是 classes。这些继承自您定义的公共接口 (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)
如果 class 有实际实例(单例对象计数),您可以使用结构类型:
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 classes 没有伴随对象 - 你最终会得到 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 // ...
创建伴生对象并使用隐式处理这些情况:
object Enum { def apply[E](implicit e: Enum[E]): Enum[E] = e implicit def buildJavaEnumInstance[E <: java.util.Enum: ClassTag] = ??? implicit def buildCoproductEnum = ??? ... }
使用
Enum
作为类型 class 或其他东西。def iNeedSomeEnumHere[E: Enum](param: String): E = Enum[E].findByName(param)
不过我同意,这需要大量的前期编码。对于图书馆来说可能是个好主意,因为我相信不仅仅是你有这个问题。