In Scala 2.13 I have a case where I define some operation for all of types extending some sealed trait EnumType
. I made it working but I'd like getTypeClass
function not to be dependant on concrete types extending EnumType
. Now I have to visit this function any time EnumType
changes and add or remove some pattern. Is there a way to get instances of Operation
type class for EnumType
types but without pattern matching on all of them?
sealed trait EnumType
case object Add10 extends EnumType
case object Add50 extends EnumType
trait Operation[ T] {
def op(a: Int): Int
}
implicit val add10: Operation[Add10.type] = (a: Int) => a 10
implicit val add50: Operation[Add50.type] = (a: Int) => a 50
def getTypeClass(enumType: EnumType): Operation[EnumType] = {
// I need to modify this pattern matching
// every time EnumType changes
enumType match {
case Add10 => implicitly[Operation[Add10.type]]
case Add50 => implicitly[Operation[Add50.type]]
}
// I'd wish it could be done with without pattern matching like (it does not compile):
// implicitly[Operation[concrete_type_of(enumType)]]
}
// value of enumType is dynamic - it can be even decoded from some json document
val enumType: EnumType = Add50
println(getTypeClass(enumType).op(10)) // prints 60
EDIT
That's the way I wish it was called without using explicit subtypes of EnumType
(using circe
in this example to decode json) :
case class Doc(enumType: EnumType, param: Int)
implicit val docDecoder: Decoder[Doc] = deriveDecoder
implicit val enumTypeDecoder: Decoder[EnumType] = deriveEnumerationDecoder
decode[Doc]("""{"enumType": "Add10", "param": 10}""").map {
doc =>
println(getTypeClass(doc.enumType).call(doc.param))
}
CodePudding user response:
def getTypeClass[T <: EnumType : Operation](t: T) = implicitly[Operation[T]]
println(getTypeClass(Add50).op(10))
In fact, you don't even need getTypeClass
at all:
def operate[T <: EnumType : Operation](t: T)(param: Int) = implicitly[Operation[T]].op(param)
println(operate(Add50)(10))
The Foo : Bar
notation I used above is equivalent to this:
def operate[T <: EnumType](t: T)(param: Int)(op: Operation[T]) = op.op(param)
Note, that you are not actually using instances of Add50
and Add10
anywhere. They could just be traits rather than objects (IMO, that better reflects the intent):
sealed trait EnumType
trait Add10 extends EnumType
trait Add50 extends EnumType
trait Operation[ T] {
def op(a: Int): Int
}
implicit val add10: Operation[Add10] = (a: Int) => a 10
implicit val add50: Operation[Add50] = (a: Int) => a 50
def operate[T <: EnumType : Operation](param: Int) =
implicitly[Operation[T]].op(param)
println(operate[Add50](10))
CodePudding user response:
Since you know that statically enumType
has type just EnumType
and you want to match based on runtime value of enumType
, in order to automate pattern matching you'll have to use some kind of reflection:
- either runtime compilation
import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox
val tb = cm.mkToolBox()
def getTypeClass(enumType: EnumType): Operation[EnumType] =
tb.eval(tb.untypecheck(tb.inferImplicitValue(
appliedType(
typeOf[Operation[_]].typeConstructor,
cm.moduleSymbol(enumType.getClass).moduleClass.asClass.toType
),
silent = false
))).asInstanceOf[Operation[EnumType]]
or
def getTypeClass(enumType: EnumType): Operation[EnumType] =
tb.eval(q"_root_.scala.Predef.implicitly[Operation[${cm.moduleSymbol(enumType.getClass)}]]")
.asInstanceOf[Operation[EnumType]]
- or a macro
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def getTypeClass(enumType: EnumType): Operation[EnumType] = macro getTypeClassImpl
def getTypeClassImpl(c: blackbox.Context)(enumType: c.Tree): c.Tree = {
import c.universe._
val cases = typeOf[EnumType].typeSymbol.asClass.knownDirectSubclasses.map(s => {
val module = s.asClass.module
cq"${pq"`$module`"} => _root_.scala.Predef.implicitly[Operation[$module.type]]"
})
q"$enumType match { case ..$cases }"
}
Since all the objects are defined at compile time I guess that a macro is better.
Covariant case class mapping to its base class without a type parameter and back