We want to create an encoder for arbitrary Enumerations
with frameless which
is basically creating a bidirectional mapping from an arbitrary Enumeration
to Byte
. Currently our less than optimal solution is to give evidence on all of our Enumeration instances so the deserializer can pick it up and call apply
on that instance, a method that creates an Enumeration
from a Byte
. We want to find a way without defining these implicit values, and rather have them automatically picked up from the E
type. As far as we know object types are in one-to-one correspondence to a single instance, so we hope there's a mechanism to do this.
For example, the following works
import frameless._
object Color extends Enumeration {
type Color = Value
val Red, Green, Blue = Value
}
object State extends Enumeration {
type State = Value
val Running, Stopped, Finished = Value
}
implicit val colorEvidence = Color // we want to spare these lines
implicit val stateEvidence = State // we want to spare these lines
implicit def enumToByteInjection[E <: Enumeration](implicit e: E):
Injection[E#Value, Byte] = Injection(_.id.toByte, e.apply(_))
Solution 1 (reflection)
This here compiles and runs when compiled with scalac 2.12.4
:
object Color extends Enumeration {
type Color = Value
val Red, Green, Blue = Value
}
object State extends Enumeration {
type State = Value
val Running, Stopped, Finished = Value
}
/** Dummy replacement with similar signature */
class Injection[A, B]()
import scala.reflect.runtime.universe.TypeTag
object ItDoesNotWorkInReplObjectsMustBeTopLevel {
implicit def enumToByteInjection[E <: Enumeration](implicit tt: TypeTag[E]): Injection[E#Value, Byte] = {
val ru = scala.reflect.runtime.universe
val classLoaderMirror = ru.runtimeMirror(getClass.getClassLoader)
val moduleSymbol = ru.typeOf[E].termSymbol.asModule
val moduleMirror = classLoaderMirror.reflectModule(moduleSymbol)
val companionObject = moduleMirror.instance.asInstanceOf[E]
println(s"/* 1 */ Materialize companion object $companionObject out of nothing!")
/* 2 */ ???
/* 3 */ // profit!
}
/** A function that requires implicit `Injection` */
def testNeedsInjection[E <: Enumeration](implicit inj: Injection[E#Value, Byte]): Unit =
println("replace ??? above to continue here")
def main(args: Array[String]): Unit = {
/** Test whether an implicit injection is constructed */
testNeedsInjection[Color.type] // compiles (crashes, as expected, but compiles)
}
}
It of course crashes because of the missing implementation at ???
, but this comes after the implicit companion object is summoned into existence.
Gotchas:
classOf[Unit].classLoader
leads to NoSuchClassException
s.TypeTag
s for the enums (shouldn't be a problem when used with concrete enums on top-level, but could become a problem if the method is supposed to be buried deep in a library but still have "access to the surface": then you would have to pull TypeTag
s through every method to the surface).Solution 2 (implicit objects)
If you have all enums under your control, then you could simply declare the enumeration objects themselves implicit
. The following compiles just nicely, all implicits are inserted as expected:
implicit object Color extends Enumeration {
type Color = Value
val Red, Green, Blue = Value
}
implicit object State extends Enumeration {
type State = Value
val Running, Stopped, Finished = Value
}
/** Dummy replacement with similar signature */
class Injection[A, B]()
implicit def enumToByteInjection[E <: Enumeration](implicit e: E): Injection[E#Value, Byte] = ???
/** A function that requires implicit `Injection` */
def needsInjection[E <: Enumeration](implicit inj: Injection[E#Value, Byte]): Unit = ???
/** Test whether an implicit injection is constructed */
needsInjection[Color.type] // compiles (crashes, as expected, but compiles)