我想在一组`condition-action'中使用Scala模式匹配的强大功能。规则。这些规则不是事先知道的,而是根据一些复杂的标准在运行时生成的。 algorithmic generation mechanism可以被认为是完全独立的,并不是这个问题的一部分,它关注如何通过Scala反射/ quasiquotes来表达这一点。
具体而言,我希望在运行时生成案例定义(通用格式case v0@x(v1,_,v2): X => f(v1,v2)
)。
对于在运行时生成的某个字符串,可能通过toolBox.parse(str)
执行此操作。但是,如果可能的话,似乎需要采用比这更大程度的类型安全性:
更具体地说,我希望案例defs与条款(Term,Var(name: Char),Lit(value:Int),Group(a: Term,b: Term,c: Term)
)的密封案例类层次结构相匹配。
例如,生成的case def通常会返回no,v0,v1,v2中的部分或全部函数:
t match {
case v0@Group(v1@_,v2@Var('a')) => Group(v2,v0,Group(v1,Var('z'),Lit(17))) // etc
}
我试图在here给出关于案例定义的quasiquotes的描述,但语法相当令人费解(并且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?
???
}
答案 0 :(得分:1)
有shapeless example构建函数调用,目的是让工具箱根据运行时类型选择类型类。
您还想动态构建逻辑,但是您在准曲线方面遇到了困难。
这样做:
// 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
转换为由Select
构建的q"name"
树;我认为某处有一种实用方法。
答案 1 :(得分:0)
这真的不觉得应该使用宏来完成。正如其他答案所指出的那样,宏应该用于编译时的安全性。这样做没有明显的好处,下面写的一般定义没有提供。
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())
}
您当然可以在运行时创建案例类,但这与创建CaseDef
(仅仅是AST树)的不一样。您基本上必须完成在代码之外可用的类/方法的步骤。
不仅如此,无处不在您使用这些新生成的类必须使用反射来实例化并调用方法,除非您在生成类型的同时生成这些方法。
你可能会说这很难。 Java和scala都是编译语言,不像python或javascript那样被解释。将类加载到运行时不是标准的或推荐的。
编辑 - 澄清后
值得澄清的是,问题更多的是如何安全地创建部分函数而不是动态生成代码。
首先让我们看看这个场景,你基本上希望在下面的语句中为你在运行时不知道的n个不同的类(可能是某些算法的输出的一部分)中的行为,
case v0@x(v1,_,v2): X => f(v1,v2))
在我们继续之前,有必要讨论一下它实际编写的内容。对于代码块,
val f: PartialFunction[String, Int] = {
case "foo" => 1
}
特别是,,scala实质上将此表单的case
语句转换为PartialFunction
,这是针对输入的某些值定义仅的值的函数。 如果未定义该点,则会返回返回类型的Option
。此类型的关键方法是isDefined
。
当将类型扩展为Any
并在类上进行匹配时,这确实有效,
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
。如果它既不会安全返回None
,则会在运行时更改函数的行为。