Scala:为 case 类 动态生成匹配子句

Scala: Dynamically generating match clauses for case classes

我想在一组“条件-动作”规则中使用 Scala 模式匹配的强大功能。这些规则是事先不知道的,而是在运行时根据一些复杂的标准生成的。 algorithmic generation mechanism 可以被认为是一个完全独立的部分,不是这个问题的一部分,这个问题与如何通过 Scala reflection/quasiquotes.

表达有关

具体来说,我希望在运行时生成案例定义(一般形式 case v0@x(v1,_,v2): X => f(v1,v2))。

大概可以通过 toolBox.parse(str) 对运行时生成的某些字符串执行此操作。但是,如果可能的话,似乎需要包含比这更大程度的类型安全:

更具体地说,我希望 case defs 与密封的 case class 术语层次结构 (Term,Var(name: Char),Lit(value:Int),Group(a: Term,b: Term,c: Term)) 相匹配。

例如,生成的 case def 通常会 return none 的某些函数,v0、v1、v2 的部分或全部:

  t match {
    case v0@Group(v1@_,v2@Var('a')) => Group(v2,v0,Group(v1,Var('z'),Lit(17))) // etc
  }

我正在尝试按照给定 here 的 case defs 的准引号描述进行操作,但是语法相当令人费解(并且 Scala 2.11 的 eclipse 拒绝向我显示类型),所以以下是我所知道的。我的具体问题嵌入代码中:

def dynamicMatch(condition: SomeType, action: SomeType, tb: ToolBox)
(t: Term): Option[Term] = {

  // Q1. What type should condition and action be for maximum
  // typesafety in the calling code? Symbols? Quasiquotes? 
  // Would they best be combined into a single actual CaseDef?

  // This is obviously a hardcoded placeholder expression, in general:
  // Q2. How to bind in t, condition and action?
  val q"$expr match { case ..$cases }" =
    q"foo match { case _ : Term => Some(expr) case _ => None }"

  val cq"$pat1 => $body1" :: cq"$pat2 => $body2" :: Nil = cases

  // Q3. how should this be invoked to return the desired result?
  ???
}

这真的不应该使用宏来完成。正如其他答案所指出的那样,宏应该用于 compile-time 安全。这样做没有具体的好处,下面写的一般定义没有提供。

case class GenericClass(`type`: String, args: List[Any])

val _actions = Map("1" -> () => println("hello"), "2" -> () => println("goodbye"))

def dynamic(gen: GenericClass) match {
   case GenericClass(n, _) => _actions.get(n).map(_.apply())
}

您当然可以在运行时创建 case classes,但是这 与创建 CaseDef 不同,后者只是一个 AST 树。您基本上必须完成 class/methods 才能在您的代码之外使用的步骤。

  1. 创建 AST 树
  2. 获取scala编译器编译成java
  3. 使用您自己的 classloader 或某种反射加载字节码来加载 java class。

不仅如此,任何地方你使用这些新生成的classes将不得不使用反射来实例化和调用这些方法,除非你同时生成这些方法作为生成类型。

您可能会说 这很难。 Java 因此 scala 都是编译语言,不像 python 或 javascript 是解释的。将 classes 加载到运行时不是标准的,也不推荐。

编辑 - 澄清后

值得澄清的是,这个问题更多地与如何安全地创建部分函数有关,而不是像最初看起来那样动态生成代码。

首先让我们来看看这个场景,您基本上希望在下面的语句中针对 n 个不同的 classes 执行您在运行时不知道的行为(大概是某些算法输出的一部分),

case v0@x(v1,_,v2): X => f(v1,v2))

在我们继续之前,有必要讨论一下这实际上编译成什么。对于代码块,

val f: PartialFunction[String, Int] = {
    case "foo" => 1
}

特别是,scala 本质上将这种形式的 case 语句转换为 PartialFunction,这是一个函数,其值定义为 的某些值输入。 如果点未定义,它将 return return 类型 Option。这种类型的关键方法是 isDefined.

当将类型扩展到 Any 并匹配 class、

时,这确实有效
val f: PartialFunction[Any, Int] = {
    case _: String => 1
    case _: Int => 2
}

这与您的问题有什么关系?那么,PartialFunction 的另一个有趣的方法是 orElse 方法。它所做的是检查是否为特定点定义了部分函数,​​如果没有,将尝试评估第二个 PartialFunction.

 case class Foo(s: String)
 case class Bar(i: Int)

 val f1: PartialFunction[Any, String] = {
   case Foo(s) => s
 }

 val f2: PartialFunction[Any, String] = {
   case Bar(i) => i.toString
 }

 //our composite!
 val g = f1 orElse f2

在上面的示例中,g 将仅评估输入是 Foo 还是 Bar。如果它不是 return 和 None 安全 并且函数的行为会在运行时更改。

有一个 shapeless example 构建函数调用的目的是让工具箱成为 select 基于运行时类型的类型类。

您还想动态构建逻辑,但在准引用方面遇到困难。

这有一些作用:

// Some classes
sealed trait Term
case class Lit(value: Int) extends Term
case class Group(a: Term, b: Term) extends Term

// Build a function that hooks up patterns to other expressions
  def stagedTermFunction(t: Term): Option[Term] = {
    // add lits
    val tt = tq"_root_.shapeless.examples.Term"
    val lit = q"_root_.shapeless.examples.Lit"
    val grp = q"_root_.shapeless.examples.Group"
    val pat = pq"$grp($lit(x), $lit(y))"
    val add = cq"$pat => Some($lit(x + y))"
    val default = cq"_ => None"
    val cases = add :: default :: Nil
    val res = q"{ case ..$cases } : (($tt) => Option[$tt])"
    val f = evalTree[Term => Option[Term]](res)
    f(t)
  }

那这不炸了:

  val t3: Term = Group(Lit(17), Lit(42))
  val Some(Lit(59)) = stagedTermFunction(t3)
  assert(stagedTermFunction(Lit(0)) == None)

如果您想操纵 symbolOf[Group],您可能必须将 sym.fullName 转换为 q"name" 构建的 Select 树;我认为某处有实用方法。