我们想用Enumerations
为任意frameless创建一个编码器
基本上是创建从任意Enumeration
到Byte
的双向映射。目前,我们不太理想的解决方案是在所有Enumeration实例上提供证据,以便解串器可以拾取它并在该实例上调用apply
,这是一种从Enumeration
创建Byte
的方法。我们希望找到一种不定义这些隐含值的方法,而是自动从E
类型中选择它们。据我们所知,对象类型与单个实例一一对应,因此我们希望有一种机制可以做到这一点。
例如,以下工作
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(_))
解决方案1(反射)
这里编译并运行时使用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)
}
}
由于???
缺少实现,它当然会崩溃,但这是在隐含的伴随对象被召唤出来之后发生的。
陷阱:
classOf[Unit].classLoader
这样的东西导致NoSuchClassException
s。TypeTag
s作为枚举(当与顶层的具体枚举一起使用时不应该是一个问题,但如果该方法被埋在库的深处但仍然具有“访问表面”的话可能会成为一个问题:那么你必须通过每种方法将TypeTag
s拉到表面)。解决方案2(隐式对象)
如果你控制了所有枚举,那么你可以简单地声明枚举对象implicit
。以下编译很好,所有implicits按预期插入:
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)
解决方案3(无形魔法)
我们也可以使用Shapeless Witness
es来为我们的Enumeration类型召唤单例值。 Shapeless使用编译时反射和代码生成来为给定类型创建实例。
import shapeless._
implicit def enumToByteInjection[E <: Enumeration](implicit w: Witness.Aux[E]):
Injection[E#Value, Byte] = Injection(_.id.toByte, w.value.apply(_))