【问题标题】:Evalutate complex type with quasiquote scala, unlifting用 quasiquotes scala 评估复杂类型,unlifting
【发布时间】:2017-12-10 22:22:16
【问题描述】:

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

我发现Liftablescala.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)")
                                ^

【问题讨论】:

    标签: scala scala-macros scala-quasiquotes


    【解决方案1】:

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

    1. 让你的论点也是Tree,即类似这样的东西
      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:
    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")
    }
    

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

    【讨论】:

    • 谢谢。问题是参数来自其他地方,我无法控制它的创建。我已经设法通过静态映射解决了这个问题,并使用硬编码键编译代码以在这个函数中访问。
    • @Oleg,对于您的案例,方法 #2 有什么问题?至于全局静态变量——最明显的问题是它本质上不是线程安全的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-28
    • 1970-01-01
    • 1970-01-01
    • 2017-01-16
    • 1970-01-01
    • 2010-11-14
    • 1970-01-01
    相关资源
    最近更新 更多