简体   繁体   中英

Covariant case class mapping to its base class without a type parameter and back

I have a trait with objects that Im using to represent an enum like:

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
}

Then I have two case classes:

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

I want to be able to map my notAContainer class to a container class by using the value it has in Status. Is there anyway I can do that? I could also change the type of status within notAContainer.

You can just do

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

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

You will have values of type container[status] (not of its subtypes container[status.pending] , container[status.ready] ).


Just in case, if it doesn't suit your use case please explain why (why you need values of types container[status.pending] , container[status.ready] , how you're going to use them etc.).


If this is really important (for example if the constructor of class container behaves differently for different S ) then for example you can specify the type parameter and downcast

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

Or you can use pattern matching

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

But the result will have type container[status] .


You can even automate the pattern matching with a macro

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)
//}

Pattern matching (manual or with the macro) occurs at runtime. So at compile time we can't have a value of types container[status.pending] , container[status.ready] , only a value of type container[status] .

If you really need a value of type container[status.pending] or container[status.ready] then you can use reflective compilation at runtime

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

Inside quasiquotes q"..." the variable c has type container[status.pending] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM