Динамическое смешивание в Scala - действительно ли это возможно?

То, чего я хотел бы достигнуть, имеет надлежащую реализацию для

def dynamix[A, B](a: A): A with B

Я могу знать, каков B, но не знайте то, что A (но если B имеет сам тип затем, я мог бы добавить некоторые ограничения на A). scala компилятор доволен вышеупомянутой подписью, но я еще не мог выяснить, как реализация будет похожа - если это будет возможно вообще.

Некоторые опции, которые прибыли по моему мнению:

  • Используя отражательный/динамичный прокси.
    • Самый простой случай: A является интерфейсом на уровне Java +, я могу инстанцировать B, и он имеет не сам тип. Я предполагаю, что это не было бы слишком твердо (если я не сталкиваюсь с некоторыми противными, неожиданными проблемами):
      создайте новый B (b), и также прокси, реализовав и A и B и с помощью делегирования обработчика вызовов или для a или для b.
    • Если B нельзя инстанцировать, я мог бы все еще создать подкласс его и сделать, как он был описан выше. Если бы это также имеет сам тип, мне, вероятно, была бы нужна некоторая делегация тут и там, но это может все еще работать.
    • Но что, если A является конкретным типом и я не могу найти надлежащий интерфейс для него?
    • Я столкнулся бы с большим количеством проблем (например, чем-то связанным с линеаризацией или специальными конструкциями, помогающими совместимости Java)?
  • Используя своего рода обертывание вместо смешивания и возврата B, доступного от b.
    К сожалению, в этом случае вызывающая сторона должна была бы знать, как вложение сделано, который мог быть довольно неудобным, если бы смешивание в/переносящий несколько раз делается (D [C [B]]), поскольку оно должно было бы найти, что правильный уровень вложения получает доступ к необходимой функциональности, таким образом, я не считаю это решением.
  • Реализация плагина компилятора. У меня есть нулевой опыт с ним, но мое инстинктивное чувство состоит в том, что это не было бы тривиально. Я думаю, что плагин Kevin Wright автопрокси имеет немного подобную цель, но это не было бы достаточно для моей проблемы (уже?).

У Вас есть какие-либо другие идеи, которые могли бы работать? Какой путь Вы рекомендовали бы? Какие "проблемы" ожидать?
Или я должен забыть это, потому что это не возможно с текущими ограничениями Scala?

Намерение позади моей проблемы: Скажите, что у меня есть бизнес-рабочий процесс, но это не слишком строго. Некоторые шаги зафиксировали порядок, но другие не делают, но в конце все они должен быть сделан (или некоторые из них требуемый для последующей обработки).
Немного более конкретный пример: у Меня есть A, я могу добавить B и C к нему. Я не забочусь, который сделан сначала, но в конце мне будет нужно с B с C.

Комментарий: Я не знаю слишком много о Groovy, но ТАК открылся этот вопрос, и я предполагаю, что это - более или менее то же как, что я хотел бы, по крайней мере, концептуальный.

40
задан Community 23 May 2017 в 12:25
поделиться

1 ответ

Я считаю, что это невозможно сделать строго во время выполнения, потому что черты смешиваются при компиляции. время в новые классы Java. Если вы смешиваете черту с существующим классом анонимно, вы можете увидеть, просмотрев файлы классов и используя javap, что анонимный класс с измененным именем создается с помощью scalac:

class Foo {
  def bar = 5
}

trait Spam {
  def eggs = 10
}

object Main {
  def main(args: Array[String]) = {
    println((new Foo with Spam).eggs)
  }
}

scalac Mixin.scala; ls * .class возвращает

Foo.class Main $ .class Spam $ class.class Main $$ anon $ 1.класс Main.class Спам.class

В то время как javap Main \ $ \ $ anon \ $ 1 возвращает

Compiled from "mixin.scala"

public final class Main$$anon$1 extends Foo implements Spam{
    public int eggs();
    public Main$$anon$1();
}

Как видите, scalac создает новый анонимный класс, который загружается во время выполнения; предположительно метод яйца в этом анонимном классе создает экземпляр Spam $ class и вызывает для него яйца , но я не совсем уверен.

Однако , здесь мы можем сделать довольно хитрый трюк:

import scala.tools.nsc._;
import scala.reflect.Manifest

object DynamicClassLoader {
  private var id = 0
  def uniqueId = synchronized {  id += 1; "Klass" + id.toString }
}

class DynamicClassLoader extends 
    java.lang.ClassLoader(getClass.getClassLoader) {
  def buildClass[T, V](implicit t: Manifest[T], v: Manifest[V]) = {

    // Create a unique ID
    val id = DynamicClassLoader.uniqueId

    // what's the Scala code we need to generate this class?
    val classDef = "class %s extends %s with %s".
      format(id, t.toString, v.toString)

    println(classDef)

    // fire up a new Scala interpreter/compiler
    val settings = new Settings(null)
    val interpreter = new Interpreter(settings)

    // define this class
    interpreter.compileAndSaveRun("<anon>", classDef)

    // get the bytecode for this new class
    val bytes = interpreter.classLoader.getBytesForClass(id)

    // define the bytecode using this classloader; cast it to what we expect
    defineClass(id, bytes, 0, bytes.length).asInstanceOf[Class[T with V]]
  }

}


val loader = new DynamicClassLoader

val instance = loader.buildClass[Foo, Spam].newInstance
instance.bar
// Int = 5
instance.eggs
// Int = 10

Поскольку вам требуется для использования компилятора Scala, AFAIK, это, вероятно, близко к самому чистому решению, которое вы могли бы сделать, чтобы получить это. Это довольно медленно, но мемоизация, вероятно, очень поможет.

Этот подход довольно нелепый, хакерский и идет вразрез с сутью языка. Я полагаю, что сюда могут закрасться всякие чудаковатые ошибки; Люди, которые использовали Java дольше меня, предупреждают о безумии, связанном с возиться с загрузчиками классов.

26
ответ дан 27 November 2019 в 01:57
поделиться
Другие вопросы по тегам:

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