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 才能在您的代码之外使用的步骤。
- 创建 AST 树
- 获取scala编译器编译成java
- 使用您自己的 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
树;我认为某处有实用方法。
我想在一组“条件-动作”规则中使用 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 才能在您的代码之外使用的步骤。
- 创建 AST 树
- 获取scala编译器编译成java
- 使用您自己的 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
树;我认为某处有实用方法。