web-dev-qa-db-ja.com

特性を動的に混合する

特性を持っている

trait Persisted {
  def id: Long
}

任意のケースクラスのインスタンスを受け入れ、特性が混在したコピーを返すメソッドを実装するにはどうすればよいですか?

メソッドのシグネチャは次のようになります。

def toPersisted[T](instance: T, id: Long): T with Persisted
38
Nikita Volkov

これはマクロ(公式にはScala 2.10.0-M3以降)の一部です)で実行できます。 これがあなたが探しているものの要点の例です

1)私のマクロは、_new T with Persisted_が行うのと同じように、提供されたケースクラスとPersistedを継承するローカルクラスを生成します。次に、引数をキャッシュして(複数の評価を防ぐため)、作成されたクラスのインスタンスを作成します。

2)どの木を生成するかをどうやって知りましたか?入力コードの解析の結果であるAST)を出力する単純なアプリparse.exeがあります。そこで、parse class Person$Persisted1(first: String, last: String) extends Person(first, last) with Persistedを呼び出し、出力を記録してマクロに再現しました。 .parse.exeは_scalac -Xprint:parser -Yshow-trees -Ystop-after:parser_のラッパーです。ASTを探索するさまざまな方法があります。詳細は "Metaprogramming in Scala 2.10"

3)scalacの引数として_-Ymacro-debug-lite_を指定すると、マクロ展開のサニティチェックを行うことができます。その場合、すべての拡張が印刷され、codegenエラーをより速く検出できるようになります。

編集します。 2.10.0-M7の例を更新しました

32
Eugene Burmako

Vanillascalaを使用して目的を達成することはできません。問題は、次のようなミックスインです。

scala> class Foo
defined class Foo

scala> trait Bar
defined trait Bar

scala> val fooWithBar = new Foo with Bar
fooWithBar: Foo with Bar = $anon$1@10ef717

Foo with Barを混合して作成しますが、実行時には実行されません。コンパイラは、新しい匿名クラスを生成するだけです。

scala> fooWithBar.getClass
res3: Java.lang.Class[_ <: Foo] = class $anon$1

詳細については、 Dynamic mixin in Scala-それは可能ですか? を参照してください。

9
Emil H

更新

[〜#〜] sorm [〜#の一部としてScala 2.10.0-RC1のツールボックスAPIを利用する、最新の実用的なソリューションを見つけることができます。 〜] プロジェクト。


次のソリューションは、Scala 2.10.0-M3リフレクションAPIとScalaインタープリターに基づいています。元のケースクラスから継承するクラスを動的に作成し、キャッシュします。特性が混在しています。最大でキャッシュのおかげで、このソリューションは元のケースクラスごとに1つのクラスのみを動的に作成し、後でそれを再利用する必要があります。

新しいリフレクションAPIはあまり公開されておらず、安定しておらず、チュートリアルもありませんが、このソリューションには愚かな反復アクションや癖が含まれている可能性があります。

次のコードはScala 2.10.0-M3でテストされました。

1.Persisted.scala

混合される特性。プログラムの更新のために少し変更したことに注意してください

trait Persisted {
  def key: String
}

2.PersistedEnabler.scala

実際のワーカーオブジェクト

import tools.nsc.interpreter.IMain
import tools.nsc._
import reflect.mirror._

object PersistedEnabler {

  def toPersisted[T <: AnyRef](instance: T, key: String)
                              (implicit instanceTag: TypeTag[T]): T with Persisted = {
    val args = {
      val valuesMap = propertyValuesMap(instance)
      key ::
        methodParams(constructors(instanceTag.tpe).head.typeSignature)
          .map(_.name.decoded.trim)
          .map(valuesMap(_))
    }

    persistedClass(instanceTag)
      .getConstructors.head
      .newInstance(args.asInstanceOf[List[Object]]: _*)
      .asInstanceOf[T with Persisted]
  }


  private val persistedClassCache =
    collection.mutable.Map[TypeTag[_], Class[_]]()

  private def persistedClass[T](tag: TypeTag[T]): Class[T with Persisted] = {
    if (persistedClassCache.contains(tag))
      persistedClassCache(tag).asInstanceOf[Class[T with Persisted]]
    else {
      val name = generateName()

      val code = {
        val sourceParams =
          methodParams(constructors(tag.tpe).head.typeSignature)

        val newParamsList = {
          def paramDeclaration(s: Symbol): String =
            s.name.decoded + ": " + s.typeSignature.toString
          "val key: String" :: sourceParams.map(paramDeclaration) mkString ", "
        }
        val sourceParamsList =
          sourceParams.map(_.name.decoded).mkString(", ")

        val copyMethodParamsList =
          sourceParams.map(s => s.name.decoded + ": " + s.typeSignature.toString + " = " + s.name.decoded).mkString(", ")

        val copyInstantiationParamsList =
          "key" :: sourceParams.map(_.name.decoded) mkString ", "

        """
        class """ + name + """(""" + newParamsList + """)
          extends """ + tag.sym.fullName + """(""" + sourceParamsList + """)
          with """ + typeTag[Persisted].sym.fullName + """ {
            override def copy(""" + copyMethodParamsList + """) =
              new """ + name + """(""" + copyInstantiationParamsList + """)
          }
        """
      }

      interpreter.compileString(code)
      val c =
        interpreter.classLoader.findClass(name)
          .asInstanceOf[Class[T with Persisted]]

      interpreter.reset()

      persistedClassCache(tag) = c

      c
    }
  }

  private lazy val interpreter = {
    val settings = new Settings()
    settings.usejavacp.value = true
    new IMain(settings, new NewLinePrintWriter(new ConsoleWriter, true))
  }


  private var generateNameCounter = 0l

  private def generateName() = synchronized {
    generateNameCounter += 1
    "PersistedAnonymous" + generateNameCounter.toString
  }


  // REFLECTION HELPERS

  private def propertyNames(t: Type) =
    t.members.filter(m => !m.isMethod && m.isTerm).map(_.name.decoded.trim)

  private def propertyValuesMap[T <: AnyRef](instance: T) = {
    val t = typeOfInstance(instance)

    propertyNames(t)
      .map(n => n -> invoke(instance, t.member(newTermName(n)))())
      .toMap
  }

  private type MethodType = {def params: List[Symbol]; def resultType: Type}

  private def methodParams(t: Type): List[Symbol] =
    t.asInstanceOf[MethodType].params

  private def methodResultType(t: Type): Type =
    t.asInstanceOf[MethodType].resultType

  private def constructors(t: Type): Iterable[Symbol] =
    t.members.filter(_.kind == "constructor")

  private def fullyQualifiedName(s: Symbol): String = {
    def symbolsTree(s: Symbol): List[Symbol] =
      if (s.enclosingTopLevelClass != s)
        s :: symbolsTree(s.enclosingTopLevelClass)
      else if (s.enclosingPackageClass != s)
        s :: symbolsTree(s.enclosingPackageClass)
      else
        Nil

    symbolsTree(s)
      .reverseMap(_.name.decoded)
      .drop(1)
      .mkString(".")
  }

}

3. Sandbox.scala

テストアプリ

import PersistedEnabler._

object Sandbox extends App {
  case class Artist(name: String, genres: Set[Genre])
  case class Genre(name: String)

  val artist = Artist("Nirvana", Set(Genre("rock"), Genre("grunge")))

  val persisted = toPersisted(artist, "some-key")

  assert(persisted.isInstanceOf[Persisted])
  assert(persisted.isInstanceOf[Artist])
  assert(persisted.key == "some-key")
  assert(persisted.name == "Nirvana")
  assert(persisted == artist)  //  an interesting and useful effect

  val copy = persisted.copy(name = "Puddle of Mudd")

  assert(copy.isInstanceOf[Persisted])
  assert(copy.isInstanceOf[Artist])
  //  the only problem: compiler thinks that `copy` does not implement `Persisted`, so to access `key` we have to specify it manually:
  assert(copy.asInstanceOf[Artist with Persisted].key == "some-key")
  assert(copy.name == "Puddle of Mudd")
  assert(copy != persisted)

}
4
Nikita Volkov

あなたがやろうとしていることはレコード連結として知られており、Scalaの型システムはサポートしていません。 (Fwiw、この機能を提供する thisthis などの型システムが存在します。)

型クラスはあなたのユースケースに合うかもしれないと思いますが、質問はあなたが解決しようとしている問題について十分な情報を提供していないので、はっきりとはわかりません。

4
missingfaktor