Вопрос по – Микширование в признаке динамически

37

Наличие черты

<code>trait Persisted {
  def id: Long
}
</code>

Как реализовать метод, который принимает экземпляр любого класса case и возвращает его копию со смешанной чертой?

Подпись метода выглядит так:

<code>def toPersisted[T](instance: T, id: Long): T with Persisted
</code>
хорошо, у вас есть свои причины, но все ответы на сегодняшний день показывают, что в Scala вам, возможно, придется пересмотреть свой подход. С каким ORM вы работаете, с кем вы написали, или с третьей стороной? virtualeyes
Это интересный вопрос, но рискуя указать очевидное, почему ваши классы дел не расширяют общую черту, которая обеспечивает идентификатор? virtualeyes
@virtualeyes Это проблема очень тонко настроенного API ORM, над которым я работаю. Пока эти объекты не реализуют эту черту, они являются просто объектами бизнес-логики без ссылки на БД, но такой подход открывает потребность в методе API, подобномdef save[T](data: T): T with Persisted, который будет опираться на метод, описанный в вопросе. Nikita Volkov
аааа, радости катиться самостоятельно ;-) С подходом Эмиля Х. как бы вы, во время компиляции, сделали "новый T с сохраненным"? Похоже, вам понадобится массивный оператор match {} (то есть, вручную укажите целевой класс), а затем, если это так, почему бы тогда просто не указать идентификатор? Хе-хе-хе, вы сами разберетесь или сдадите и пойдете со ScalaQuery ;-) virtualeyes
@virtualeyes Это новый проект ORM, над которым я работаю. Я не думаю, что это невозможно, я просто думаю, что это будет сложно, вероятно, будет связано с манипуляциями с байт-кодом. Как только будет найдено решение, я опубликую его или выберу здесь. Эмиль Н сделал хорошее предложение, и я попытаюсь развить это. Nikita Volkov

Ваш Ответ

5   ответов
4
Update

которое использует API Toolboxes из Scala 2.10.0-RC1 как частьСОРМ проект.

Следующее решение основано на API отражения Scala 2.10.0-M3 и Scala Interpreter. Он динамически создает и кэширует классы, унаследованные от исходных классов case, с добавленной характеристикой. Благодаря максимальному кешированию это решение должно динамически создавать только один класс для каждого исходного класса case и использовать его позже.

Since the new reflection API isn't that much disclosed nor is it stable and there are no tutorials on it yet this solution may involve some stupid repitative actions and quirks.

Следующий код был протестирован с Scala 2.10.0-M3.

1. Persisted.scala

Черта, которая будет смешана в.Please note that I've changed it a bit due to updates in my program

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)

}
Кроме того, что именно чувствует черная магия? Является ли это только необходимостью интеграции со средствами сборки или это что-то еще?
Если вам неудобно работать с макросами, вы можете использовать новый API-набор инструментов, который позволяет вам компилировать AST и гарантирует обратную совместимость с интерпретатором. Вы можете скопировать / вставить мой код манипулирования деревом, а затем использовать scala.reflect.mirror.mkToolBox (). RunExpr (...) для его компиляции и запуска.
@EugeneBurmako Большое спасибо за предложение о наборе инструментов, я обязательно его проверю! О черной магии. Это означает, что без приличных учебных пособий или документации очень трудно понять, что происходит. Кроме того, API для создания AST выглядит так, как будто он разработан с определенной целью причинения боли, хотя я также не могу сказать ничего лучшего об API отражения, но я понимаю, что все это вызвано смешением с миром компиляторов. Необходимость вручную управлять процессом компиляции - это слишком много, поэтому лучше оставить эту проблему нерешенной. Nikita Volkov
31

ав Scala начиная с 2.10.0-M3).Вот основной пример того, что вы ищете.

1) Мой макрос генерирует локальный класс, который наследует от предоставленного класса case и Persisted, очень похоже наnew T with Persisted сделал бы. Затем он кэширует свой аргумент (для предотвращения множественных оценок) и создает экземпляр созданного класса.

2) Как я узнал, какие деревья генерировать? У меня есть простое приложение, parse.exe, которое печатает AST, полученный в результате анализа входного кода. Так что я только что призвал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, подробнее в«Метапрограммирование в Scala 2.10».

3) Расширения макросов могут быть проверены, если вы предоставите-Ymacro-debug-lite в качестве аргумента для скаляка. В этом случае все расширения будут распечатаны, и вы сможете быстрее обнаруживать ошибки в коде.

редактировать. Обновлен пример для 2.10.0-M7

Согласен, отдельная компиляция неудобна. Я посмотрю, что я могу с этим поделать.
Хотя этот материал чрезвычайно интересен, он также такой хардкорный. Как мне объединить эти несколько этапов компиляции с Maven? Это станет более доступным, когда релиз появится здесь? Nikita Volkov
Я думаю, что он будет работать с Maven без проблем, вы можете отправить аргументы компилятора:scala-tools.org/mvnsites/maven-scala-plugin/… , У меня есть рабочий пример с Gradle на основе Expectify:github.com/flatMapDuke/TestMacro/commit/…, Я немного впечатлен манипуляциями с деревом, но это здорово :)
4

что вы пытаетесь сделать, известно как конкатенация записей, то, что система типов Scala не поддерживает. (Fwiw, существуют системы типов - такие какэтот а такжеэтот - которые предоставляют эту функцию.)

Я думаю, что классы типов могут соответствовать вашему варианту использования, но я не могу точно сказать, так как этот вопрос не дает достаточной информации о том, какую проблему вы пытаетесь решить.

Вы не можете объединить классы / черты в Scala, вот что я имел в виду. (Я думал, что это было очевидно из контекста?)
Да, я знаю об этом. Мы говорили об этом раньше.
На самом деле,you can encode extensible records в системе типов Scala, но это не поможет напрямую с ответом на этот вопрос, которого я боюсь.
Хорошо, хорошо, но вы сказали прямо противоположное в своем ответе?
Такие записиcan be encoded в Скале. Это отличается от того, что они являются первоклассной конструкцией в языке.
9

что миксины похожи на следующее:

scala> class Foo
defined class Foo

scala> trait Bar
defined trait Bar

scala> val fooWithBar = new Foo with Bar
fooWithBar: Foo with Bar = [email protected]

создатьFoo with Bar смешано, но это не делается во время выполнения. Компилятор просто генерирует новый анонимный класс:

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

УвидетьДинамический миксин в Scala - это возможно? для получения дополнительной информации.

@KevinWright Есть открытый билет на это?
@jiawei Зачем мне создавать заявку на единственное изменение архитектуры, происходящее в моем собственном проекте?
Можно ожидать, что полностью готовая к макросам версия будет готова к выпуску 2.11, надеюсь, она у меня будет вовремя для RC1
@NikitaVolkov Вы также можете взглянуть на Autoproxy.github.com/scala-incubator/autoproxy-plugin/wiki но я не уверен в его текущем состоянии.
@KevinWright Я ошибочно предположил, что он сливается с базовой библиотекой Scala.
-1

у вас могут быть очень широкие тесты, чтобы определить, принадлежит ли объект определенной композиции, используя псевдонимы типов и структуры определения:

  type Persisted = { def id: Long }

  class Person {
    def id: Long = 5
    def name = "dude"
  }

  def persist(obj: Persisted) = {
    obj.id
  }

  persist(new Person)

Любой объект сdef id:Long будет квалифицироваться как сохраненный.

Достижение того, что, я думаю, вы пытаетесь сделать, возможно с помощью неявных преобразований:

  object Persistable {
    type Compatible = { def id: Long }
    implicit def obj2persistable(obj: Compatible) = new Persistable(obj)
  }
  class Persistable(val obj: Persistable.Compatible) {
    def persist() = println("Persisting: " + obj.id)
  }

  import Persistable.obj2persistable
  new Person().persist()
Извините, но это не имеет отношения к вопросу. Nikita Volkov

Похожие вопросы