从实例化的类(Scala)中访问值的父命名?

时间:2016-11-08 02:48:23

标签: java scala reflection introspection

假设Scala 2.11。我正在编写一个将持久保存Scala值的类。它的意图是这样使用:

class ParentClass {

  val instanceId: String = "aUniqueId"

  val statefulString: Persisted[String] = persisted { "SomeState" }

  onEvent {
    case NewState(state) => statefulString.update(state)
  }

}

Persisted是一个带有类型参数的类,用于保存特定值,如缓存,Persist处理与持久性相关的所有逻辑。但是,为了简化实现,我希望能够检索有关它实例化的信息。例如,如果父类中的实例名为statefulString,那么如何从Persisted类本身访问该名称?

这样做的目的是防止在简化API时自动命名持久值的冲突。我不能依赖于使用类型,因为可能有多个String类型的值。

感谢您的帮助!

修改

这个问题可能会有所帮助:How can I get the memory location of a object in java?

编辑2

阅读ScalaCache的源代码后,似乎有办法通过WeakTypeTag执行此操作。有人可以解释其宏中究竟发生了什么吗?

https://github.com/cb372/scalacache/blob/960e6f7aef52239b85fa0a1815a855ab46356ad1/core/src/main/scala/scalacache/memoization/Macros.scala

1 个答案:

答案 0 :(得分:0)

我能够在Scala宏和反射的帮助下完成这项工作,并调整ScalaCache中的一些代码:

class Macros(val c: blackbox.Context) {
  import c.universe._

  def persistImpl[A: c.WeakTypeTag, Repr: c.WeakTypeTag](f: c.Tree)(keyPrefix: c.Expr[ActorIdentifier], scalaCache: c.Expr[ScalaCache[Repr]], flags: c.Expr[Flags], ec: c.Expr[ExecutionContext], codec: c.Expr[Codec[A, Repr]]) = {
    commonMacroImpl(keyPrefix,  scalaCache, { keyName =>
      q"""_root_.persistence.sync.caching($keyName)($f)($scalaCache, $flags, $ec, $codec)"""
    })
  }

  private def commonMacroImpl[A: c.WeakTypeTag, Repr: c.WeakTypeTag](keyPrefix: c.Expr[ActorIdentifier], scalaCache: c.Expr[ScalaCache[Repr]], keyNameToCachingCall: (c.TermName) => c.Tree): Tree = {

    val enclosingMethodSymbol = getMethodSymbol()
    val valNameTree = getValName(enclosingMethodSymbol)

    val keyName = createKeyName()
    val scalacacheCall = keyNameToCachingCall(keyName)
    val tree = q"""
          val $keyName = _root_.persistence.KeyStringConverter.createKeyString($keyPrefix, $valNameTree)
          $scalacacheCall
        """
    tree
  }

  /**
    * Get the symbol of the method that encloses the macro,
    * or abort the compilation if we can't find one.
    */
  private def getValSymbol(): c.Symbol = {

    def getValSymbolRecursively(sym: Symbol): Symbol = {
      if (sym == null || sym == NoSymbol || sym.owner == sym)
        c.abort(
          c.enclosingPosition,
          "This persistence block does not appear to be inside a val. " +
            "Memoize blocks must be placed inside vals, so that a cache key can be generated."
        )
      else if (sym.isTerm)
        try {
          val termSym = sym.asInstanceOf[TermSymbol]
          if(termSym.isVal) termSym
          else getValSymbolRecursively(sym.owner)
        } catch {
          case NonFatal(e) => getValSymbolRecursively(sym.owner)
        }
      else
        getValSymbolRecursively(sym.owner)
    }

    getValSymbolRecursively(c.internal.enclosingOwner)
  }

  /**
    * Convert the given method symbol to a tree representing the method name.
    */
  private def getValName(methodSymbol: c.Symbol): c.Tree = {
    val methodName = methodSymbol.asMethod.name.toString
    // return a Tree
    q"$methodName"
  }

  private def createKeyName(): TermName = {
    // We must create a fresh name for any vals that we define, to ensure we don't clash with any user-defined terms.
    // See https://github.com/cb372/scalacache/issues/13
    // (Note that c.freshName("key") does not work as expected.
    // It causes quasiquotes to generate crazy code, resulting in a MatchError.)
    c.freshName(c.universe.TermName("key"))
  }

}