Evalutate复杂类型与quasiquote scala,unlifting

时间:2017-12-10 22:22:17

标签: scala scala-macros scala-quasiquotes

我需要编译函数,然后使用List[Map[String, AnyRef]]类型的不同参数对其进行评估。 我有以下代码,不使用这种类型编译,但编译简单类型,如List[Int]

我发现Liftable

中只有scala.reflect.api.StandardLiftables.StandardLiftableInstances的某些实现
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._
import scala.tools.reflect.ToolBox


val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()

val functionWrapper =
"""
  object FunctionWrapper {

  def makeBody(messages: List[Map[String, AnyRef]]) = Map.empty

   }""".stripMargin

val functionSymbol = 
tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])

val list: List[Map[String, AnyRef]] = List(Map("1" -> "2"))

tb.eval(q"$functionSymbol.function($list)")

为此获取编译错误,我该如何使其工作?

Error:(22, 38) Can't unquote List[Map[String,AnyRef]], consider using 
... or providing an implicit instance of 
Liftable[List[Map[String,AnyRef]]]
tb.eval(q"$functionSymbol.function($list)")
                                ^

1 个答案:

答案 0 :(得分:0)

问题不是来自复杂的类型,而是来自尝试使用AnyRef。当您取消引用某些文字时,这意味着您希望基础结构能够创建有效的语法树来创建与您传递的对象完全匹配的对象。不幸的是,这显然不可能适用于所有对象。例如,假设您已将Thread.currentThread()的引用作为Map的一部分传递。怎么可能工作?编译器无法重新创建这样一个复杂的对象(更不用说使它成为当前的线程)。所以你有两个明显的选择:

  1. 让你的论点也是Tree,就像这样
  2.   def testTree() = {
        val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
    
        val functionWrapper =
          """
            |  object FunctionWrapper {
            |
            |    def makeBody(messages: List[Map[String, AnyRef]]) = Map.empty
            |
            |  }
          """.stripMargin
    
        val functionSymbol =
          tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])
    
        //val list: List[Map[String, AnyRef]] = List(Map("1" -> "2"))
        val list = q"""List(Map("1" -> "2"))"""
    
        val res = tb.eval(q"$functionSymbol.makeBody($list)")
        println(s"testTree = $res")
      }
    

    这种方法的明显缺点是你在编译时丢失了类型安全性,并且可能需要为树提供大量上下文

    1. 另一种方法是不要尝试将包含AnyRef的任何内容传递给编译器基础结构。这意味着您创建了一些类似函数的Wrapper
    2. package so {
      
        trait Wrapper {
          def call(args: List[Map[String, AnyRef]]): Map[String, AnyRef]
        }
      
      }
      

      然后让生成的代码返回Wrapper而不是直接执行逻辑,并从通常的Scala代码而不是编译代码中调用Wrapper。像这样:

      def testWrapper() = {
        val tb = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
      
        val functionWrapper =
          """
            |object FunctionWrapper {
            |  import scala.collection._
            |  import so.Wrapper /* <- here probably different package :) */
            |
            |  def createWrapper(): Wrapper = new Wrapper {
            |    override def call(args: List[Map[String, AnyRef]]): Map[String, AnyRef] = Map.empty
            |  }
            |}
            | """.stripMargin
      
      
        val functionSymbol = tb.define(tb.parse(functionWrapper).asInstanceOf[tb.u.ImplDef])
      
        val list: List[Map[String, AnyRef]] = List(Map("1" -> "2"))
      
        val tree: tb.u.Tree = q"$functionSymbol.createWrapper()"
        val wrapper = tb.eval(tree).asInstanceOf[Wrapper]
        val res = wrapper.call(list)
        println(s"testWrapper = $res")
      }
      

      P.S。我不确定你在做什么,但要注意性能问题。 Scala是一种难以编译的语言,因此编译自定义代码可能比运行它更容易。如果性能成为问题,您可能需要使用其他一些方法,例如完整的宏代码生成或至少缓存已编译的代码。