Reputation: 4781
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(_))
Upvotes: 1
Views: 549
Reputation: 4781
Solution 3 (shapeless magic)
We can also use Shapeless Witness
es to summon the singleton value for our Enumeration type. Shapeless uses compile time reflection and code generation to create an instance for the given type.
import shapeless._
implicit def enumToByteInjection[E <: Enumeration](implicit w: Witness.Aux[E]):
Injection[E#Value, Byte] = Injection(_.id.toByte, w.value.apply(_))
Upvotes: 1
Reputation: 44957
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)
Upvotes: 3