Suppose I have several auto-generated classes, like MyEnum1
, MyEnum2
, ... (they are not necessarily Scala enum types, just some auto-generated classes). Though the type of MyEnum1
is different than the type of MyEnum2
(and they share no auto-generated parent types except Any
), I can guarantee that all of these auto-generated types have exactly the same public, static methods available, in particular findById
and findByName
, which allow looking up the enum value based on index or string name.
I am trying to create a function that would utilize the type-specific version of findById
and findByName
, but is generic to accept any of MyEnum1
, MyEnum2
, ... as the function parameter.
Note that a typical sealed trait
+ case class
pattern to create a sum type out of the different enums would not help here, because I am talking about dispatching different static methods based on a type parameter, and there is never any actual value parameter involved at all.
For example, suppose that MyEnum1
encodes male/female gender. So that MyEnum1.findById(0)
return MyEnum1.Female
which has type MyEnum1
. And say MyEnum2
encodes eye color, so that MyEnum2.findById(0)
returns MyEnum2.Green
which has type MyEnum2
.
I am given a Map where the key is the type and the value is the index to look up, such as
val typeMap = Map(
MyEnum1 -> 0,
MyEnum2 -> 0
)
and I would like to generically do this:
for ( (elemType, idx) <- typeMap ) yield elemType.findById(v)
|---------------|
the goal is to
avoid boilerplate
of defining this
with different
pattern matching
for every enum.
and get back some sequence type (can have element type Any
) that looks like
MyEnum1.Female, MyEnum2.Green, ...
I've struggled with the sealed trait
+ case class
boilerplate for a while and it does not seem to be conceptually the right way. No matter if I wrap values of MyEnum1
or MyEnum2
into case class value constructors like FromMyEnum1(e: MyEnum1)
and try to define implicits for operating on that value, it doesn't help in my code example above when I want to do elemType.findById(...)
, because the compiler still says that type Any
(what it resolves for the key type in my Map
), has no method findById
.
I'd strongly prefer to not wrap the types themselves in a case class pattern to serve as the keys, but I could do that -- except I cannot see how it's possible to treat the type itself as a first class value in a case class constructor, something naively like
case class FromMyEnum1(e: MyEnum1.getClass) extends EnumTrait
(so that the Map
keys could have type EnumTrait
and presumably there could be some implicit that matched each case class constructor to the correct implementation of findById
or findByName
).
Any help to understand how Scala enables using types themselves as the values inside of case class value constructors would be appreciated!
There are some fundamental misconceptions in your question.
Firstly, there are no "static methods" in Scala, all methods are attached to an instance of a class. If you want a method that is the same for every instance of a class you add a method to the companion object for that class and call it on that object.
Secondly, you can't call a method on a type, you can only call a method on an instance of a type. So you can't call findById
on one of your MyEnum
types, you can only call it on an instance of one of those types.
Thirdly, you can't return a type from a method, you can only return an instance of a type.
It is difficult to tell exactly what you are trying to achieve, but I suspect that MyEnum1
, MyEnum2
should be objects, not classes. These inherit from the common interface you have defined (findById
, findByName
). Then you can create a Map
from an instance of the common type to an index to be used in the findById
call.
Sample code:
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)
If you can't provide a common parent trait
, use a structural type:
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)