0

我有一个我用来表示枚举的对象的特征,例如:

sealed trait status extends Product with Serializable

object status{
  case object pending extends status
  case object ready extends status
  type ready = ready.type
  type pending = pending.type
}

然后我有两个案例类:

case class container[+S <: Status](status : S, commonValue: String)
case class notAContainer(status : Status, commonValue:String)

我希望能够使用它在 Status 中的值将我的 notAContainer 类映射到容器类。反正我能做到吗?我还可以更改 notAContainer 中的状态类型。

4

1 回答 1

0

你可以做

val nc: notAContainer = notAContainer(status.pending, "abc")
val nc1: notAContainer = notAContainer(status.ready, "def")

container(nc.status, nc.commonValue)
container(nc1.status, nc1.commonValue)

您将拥有类型的值container[status](而不是其子类型container[status.pending], container[status.ready])。


以防万一,如果它不适合您的用例,请解释原因(为什么需要 types 的值container[status.pending]container[status.ready]如何使用它们等)。


如果这真的很重要(例如,如果类的构造函数对不同的container行为不同S),那么例如您可以指定类型参数并向下转换

container[status.pending](nc.status.asInstanceOf[status.pending], nc.commonValue)
container[status.ready](nc1.status.asInstanceOf[status.ready], nc1.commonValue)

或者你可以使用模式匹配

nc.status match {
  case s: status.ready => container[status.ready](s, nc.commonValue)
  case s: status.pending => container[status.pending](s, nc.commonValue)
}

但结果将有 type container[status]


您甚至可以使用宏自动进行模式匹配

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def matchStatus(nc: notAContainer): container[status] = macro matchStatusImpl

def matchStatusImpl(c: blackbox.Context)(nc: c.Tree): c.Tree = {
  import c.universe._
  val s = TermName(c.freshName("s"))
  val cases = typeOf[status].typeSymbol.asClass.knownDirectSubclasses.map(symb => {
    val typ = symb.asType.toType
    val pattern = pq"$s: $typ"
    cq"$pattern => container.apply[$typ]($s, $nc.commonValue)"
  })
  q"""
    $nc.status match {
      case ..$cases
    }
  """
}

matchStatus(nc)
//scalac: App.this.nc.status match {
//  case (s$macro$1 @ (_: App1.status.pending.type)) => App1.container.apply[App1.status.pending.type](s$macro$1, App.this.nc.commonValue)
//  case (s$macro$1 @ (_: App1.status.ready.type)) => App1.container.apply[App1.status.ready.type](s$macro$1, App.this.nc.commonValue)
//}

模式匹配(手动或使用宏)发生在运行时。所以在编译时我们不能有 types 的值container[status.pending]container[status.ready]只有 type 的值container[status]

如果你真的需要一个类型的值,container[status.pending]或者container[status.ready]你可以在运行时使用反射编译

import scala.reflect.runtime.{currentMirror => cm}
import scala.reflect.runtime.universe.Quasiquote
import scala.tools.reflect.ToolBox

object App {
  val tb = cm.mkToolBox()

  sealed trait status extends Product with Serializable

  object status{
    case object pending extends status
    case object ready extends status
    type ready = ready.type
    type pending = pending.type
  }

  case class container[+S <: status](status : S, commonValue: String)
  case class notAContainer(status : status, commonValue:String)

  val nc: notAContainer = notAContainer(status.pending, "abc")

  def main(args: Array[String]): Unit = {
//    tb.eval(tb.parse(
//      s"""import App._
//         |val c = container[status.${nc.status}](status.${nc.status}, "${nc.commonValue}")
//         |println(c)
//         |""".stripMargin))

//    val clazz = nc.status.getClass
//    val classSymbol = cm.classSymbol(clazz)
    val classSymbol = cm.reflect(nc.status).symbol
//    val moduleSymbol = cm.moduleSymbol(clazz)
    val moduleSymbol = classSymbol.owner.info.decl(classSymbol.name.toTermName) // (*)
    tb.eval(q"""
      import App._
      val c = container[${classSymbol.toType}]($moduleSymbol, ${nc.commonValue})
      println(c)
    """)
  }
}

(*) 1 2

在 quasiquotes 内q"...",变量c具有 type container[status.pending]

于 2021-10-13T16:00:04.323 回答