Scala:动态生成案例类的匹配子句

时间:2016-01-16 11:40:04

标签: scala scala-quasiquotes scala-reflect

我想在一组`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?
  ???
}

2 个答案:

答案 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树)的不一样。您基本上必须完成在代码之外可用的类/方法的步骤。

  1. 创建AST树
  2. 获取scala编译器将其编译为java
  3. 使用您自己的类加载器或某种反射来加载java类以加载字节代码。
  4. 不仅如此,无处不在您使用这些新生成的类必须使用反射来实例化并调用方法,除非您在生成类型的同时生成这些方法。

    你可能会说这很难。 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 ,则会在运行时更改函数的行为。