将类型的完全限定名称转换为类型

时间:2015-04-15 14:33:33

标签: scala shapeless

我有一个密封的特征/抽象类层次结构,需要将其子类型转换为字符串,并将这些字符串转换回类型。此代码示例描述了我想要实现的目标:

import shapeless._

object Test extends App {

  sealed abstract class C(val i: Int)
  case object O1 extends C(1)
  case object O2 extends C(2)
  // lots of other implementations

  trait TC[A] {
    def f: String
  }
  implicit object TC1 extends TC[O1.type] {
    def f = "O1"
  }
  implicit object TC2 extends TC[O2.type] {
    def f = "O2"
  }
  object fqn {
    val o1 = implicitly[TC[O1.type]].f
    val o2 = implicitly[TC[O2.type]].f

    def asString(c: C): String = c match {
      case O1 ⇒ o1
      case O2 ⇒ o2
    }

    def fromString(s: String): C = s match {
      case `o1` ⇒ O1
      case `o2` ⇒ O2
    }
  }

  object asString extends Poly1 {
    private implicit def impl[A <: C : TC] = at[A](a ⇒ implicitly[TC[A]].f)
    def apply(c: C): String = Generic[C].to(c).map(this).unify
  }
  object fromString {
    def apply(s: String): C = ???
  }

  // This works as expected
  println(O1 eq fqn.fromString(fqn.asString(O1)))
  println(O2 eq fqn.fromString(fqn.asString(O2)))

  // Does not yet work
  println(O1 eq fromString(asString(O1)))
  println(O2 eq fromString(asString(O2)))
}

目前,一切都与fqn中的功能完美配合,但对于C的更多子类型来说,这很麻烦且难以维护。由于没有形状,我设法逃脱了asString部分,但我仍然在寻找fromString部分的解决方案。

任何人都可以想到在无形(或其他库)中实现fromString部分的方法吗?

关于我的例子的更多细节:

  • TC由图书馆提供,我无法对其进行更改。类型类是 由此库中的宏生成,因此是TCN类型类 不要直接存在。
  • 我无法轻松提供生成fqn.asStringfqn.fromString实施的宏,因此我正在寻找已支持此行为的库。

2 个答案:

答案 0 :(得分:0)

不幸的是,我不知道如何使用宏\ shapeless magic在编译时解决你的问题。但在运行时,解决方案可能是:

import reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe._

def fromString(s: String): C = {
  val caseObjects = weakTypeOf[C].baseClasses.
     flatMap(s => s.asClass.knownDirectSubclasses)
  val map = caseObjects.map(obj => (obj.name.toString, obj)).toMap
  cm.reflectModule(map(s).companionSymbol.asModule).instance match {
    case c: C => c
  }
}

也许您必须添加isModule条件才能获得case objects

答案 1 :(得分:0)

我设法找到了解决方案:

import shapeless._

object GetAllSingletons {
  sealed trait AllSingletons[A, C <: Coproduct] {
    def values: List[A]
  }

  implicit def cnilSingletons[A]: AllSingletons[A, CNil] =
    new AllSingletons[A, CNil] {
      def values = Nil
    }

  implicit def coproductSingletons[A, H <: A, T <: Coproduct](implicit
    witness: Witness.Aux[H],
    tsc: AllSingletons[A, T]
  ): AllSingletons[A, H :+: T] =
    new AllSingletons[A, H :+: T] {
      def values = witness.value :: tsc.values
    }

  final class GetAllSingletones[A] {
    def apply[C <: Coproduct]()(implicit
      gen: Generic.Aux[A, C],
      singletons: AllSingletons[A, C]
    ): List[A] =
      singletons.values
  }

  def singletons[A] = new GetAllSingletones[A]
}

object Test extends App {

  sealed abstract class C(val i: Int)
  case object O1 extends C(1)
  case object O2 extends C(2)
  // lots of other implementations

  trait TC[A] {
    def f: String
  }
  implicit object TC1 extends TC[O1.type] {
    def f = "O1"
  }
  implicit object TC2 extends TC[O2.type] {
    def f = "O2"
  }

  object asString extends Poly1 {
    private implicit def impl[A <: C : TC] = at[A](a ⇒ implicitly[TC[A]].f)
    def apply(c: C): String = Generic[C].to(c).map(this).unify
  }
  object fromString {
    val singletons: Map[String, C] = GetAllSingletons.singletons[C]().map(s ⇒ asString(s) → s)(collection.breakOut)
    def apply(s: String): C = singletons(s)
  }

  println(O1 eq fromString(asString(O1)))
  println(O2 eq fromString(asString(O2)))
}

GetAllSingletons中的定义很麻烦,但我找不到进一步简化它的方法。这个想法来自this questionAnother question的答案提供了一个更简单的解决方案,但它只能以更有限的方式运行。