3

我想在一组“条件动作”规则中使用 Scala 模式匹配的强大功能。这些规则不是事先知道的,而是根据一些复杂的标准在运行时生成的。算法生成机制可以被认为是完全独立的,不是这个问题的一部分,它涉及如何通过 Scala 反射/准引用来表达这一点。

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

toolBox.parse(str)对于在运行时生成的某些字符串,大概可以做到这一点。但是,如果可能的话,似乎需要包含比这更大程度的类型安全:

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

例如,生成的案例 def 通常会返回一些无函数、部分或全部 v0、v1、v2:

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

我正在尝试按照此处给出的 case defs 的 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?
  ???
}
4

2 回答 2

1

这真的不应该使用宏来完成。正如其他答案所指出的那样,宏应该用于编译时安全。这样做没有任何实际的好处,下面写的一般定义没有提供。

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 树不同。您基本上必须完成这些步骤才能使类/方法在您的代码之外可用。

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

不仅如此,在任何使用这些新生成的类的地方都必须使用反射来实例化和调用方法,除非您在生成类型的同时生成了这些方法。

正如您可能知道的那样,这很困难。Java 和因此 scala 都是编译语言,这与解释的 python 或 javascript 不同。将类加载到运行时不是标准的或推荐的。

编辑 - 澄清后

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

首先让我们来看这个场景,你基本上希望在下面的语句中具有你在运行时不知道的 n 个不同类的行为(可能是某些算法输出的一部分),

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

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

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

特别是,scala 本质上将case这种形式的语句转换为 a PartialFunction,这是一个针对输入的某些值定义的值的函数。在未定义点的情况下,它将返回Option返回类型的 an 。这种类型的关键方法是isDefined

这在将类型扩大到类Any并匹配类时确实有效,

val f: PartialFunction[Any, Int] = {
    case _: String => 1
    case _: Int => 2
}

这与您的问题有什么关系?嗯,另一个有趣的方法PartialFunctionorElse方法。它的作用是检查是否为特定点定义了偏函数,如果不是,将尝试评估第二个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只会评估输入是否为FooBar。如果两者都不是,它将None 安全地返回 a并且函数的行为在运行时发生更改。

于 2016-01-18T23:06:13.150 回答
1

有一个无形示例构建了一个函数调用,目的是让工具箱根据运行时类型选择一个类型类。

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

这会做一些事情:

// 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.fullNameSelect树。q"name"我认为某处有一种实用方法。

于 2016-01-23T08:58:46.460 回答