Scala 反射:如何在运行时定义案例类,然后引用它?

问题描述 投票:0回答:2

我想在运行时定义一个案例类,例如

val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
val myClass: ClassDef = q"case class Authentication(email: String)".asInstanceOf[ClassDef]
val definedClass  = tb.define(myClass)

然后能够在另一个反思中引用它

  // Actor code that recognise the defined case object
  val actorCode = q"""
  import akka.actor._
  object HelloActor {
    def props() = Props(new HelloActor())
  }
  class HelloActor() extends Actor {
    def receive = {
      case $definedClass(emailParam)  => println("case object instance has been received!")
      case _       => println("None received!")
    }
  }
  return HelloActor.props()
  """

知道如何做到这一点吗?

java scala reflection scala-macros
2个回答
1
投票

您显示的示例无需任何编译时反射即可实现:

import akka.actor._

// define extractor object: https://docs.scala-lang.org/tour/extractor-objects.html
sealed trait EmailExtractor[A] {
  def unapply(value: A): Option[String]
}
object EmailExtractor {
  
  def of[A](pf: PartialFunction[A, String]) = new EmailExtractor[A] {
    def unapply(value: A): Option[String] = pf.lift(value)
  }
}

// inject difference in behavior via constructor
class HelloActor[A](emailExtractor: EmailExtractor[A]) extends Actor {

  def receive = {
    case emailExtractor(emailParam) =>
      println("case class instance has been received!")
    case _ => println("None received!")
  }
}
object HelloActor {
  
  def props[A](emailExtractor: EmailExtractor[A]) =
    Props(new HelloActor(emailExtractor))
}

implicit val actorSystem: ActorSystem = ActorSystem()

// you'd have to define message somewhere available to both:
//  * actor you are creating
//  * place where you are sending a message
// anyway, there is no reason to generate a whole new actor because of it

def oneTimeCaseClass1 = {
  case class Message(email: String)
  
  actorSystem.actorOf(HelloActor.props(EmailExtractor.of[Message] {
    case Message(string) => string
  })) ! Message("[email protected]")
}

def oneTimeCaseClass2 = {
  case class Message(email: String)
  
  actorSystem.actorOf(HelloActor.props(EmailExtractor.of[Message] {
    case Message(string) => string
  })) ! Message("[email protected]")
}

oneTimeCaseClass1
oneTimeCaseClass2

scala.concurrent.Await.result(
  actorSystem.terminate(),
  scala.concurrent.duration.Duration.Inf
)

(参见scastie

另外:

  • return
    是一种糟糕的代码实践
  • Akka Classic(无类型)不鼓励使用 Akka Typed
  • 编译时反射的价值恰恰在于它发生在编译时——准引号不能在运行时使用来定义新代码,这需要一个成熟的编译器结合将其输出提供给类的特定方式加载器(所以基本上嵌入 REPL) - 这是危险的(它基本上是任意代码执行),容易出现 OOM(因为如果 REPL 将其整个历史记录存储在内存中,则无法收集内存)并且没有必要

1
投票

你的代码看起来几乎没问题。

您刚刚错过了

.companion
中的

val actorCode = q"""
   import akka.actor._
   object HelloActor {
     def props() = Props(new HelloActor())
   }
   class HelloActor() extends Actor {
     def receive = {
       case ${definedClass.companion}(emailParam)  => println("case object instance has been received!")
       case _       => println("None received!")
     }
   }
   ActorSystem("hellokernel").actorOf(HelloActor.props()) ! ${definedClass.companion}("abc")
 """

tb.eval(actorCode) // case object instance has been received!

斯卡拉 2.13.8

© www.soinside.com 2019 - 2024. All rights reserved.