Mixin dynamic en Scala: ¿es posible?

Lo que me gustaría lograr es tener una implementación adecuada para

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

Puedo saber qué es B, pero no sé qué es A (pero si B tiene un tipo propio, entonces podría agregar algunas restricciones en A). El comstackdor scala está contento con la firma anterior, pero aún no podía entender cómo sería la implementación, si es posible.

Algunas opciones que se me vinieron a la mente:

  • Usando reflexión / proxy dynamic.
    • Caso más simple: A es una interfaz en el nivel de Java + Puedo crear una instancia de B y no tiene un tipo propio. Supongo que no sería demasiado difícil (a menos que me encuentre con algunos problemas desagradables e inesperados):
      cree un nuevo B (b), y también un proxy implementando ambos A y B y usando un manejador de invocación delegando a a o b.
    • Si no se puede crear una instancia de B todavía podría crear una subclase y hacer lo que se describió anteriormente. Si también tiene un tipo propio, probablemente necesite una delegación aquí y allá, pero aún puede funcionar.
    • Pero, ¿y si A es un tipo concreto y no puedo encontrar una interfaz adecuada para ello?
    • ¿Me encontraría con más problemas (por ejemplo, algo relacionado con la linealización o construcciones especiales que ayudan a la interoperabilidad de Java)?
  • Usando un tipo de envoltura en lugar de mixin y devuelve B [A], se puede acceder a a desde b.
    Desafortunadamente, en este caso, la persona que llama debería saber cómo se realiza el anidamiento, lo que podría ser bastante inconveniente si la mezcla / envoltura se realiza varias veces (D [C [B [A]]] ya que necesitaría encontrar el nivel correcto de anidación para acceder a la funcionalidad necesaria, por lo que no lo considero una solución.
  • Implementando un plugin de comstackdor. No tengo ninguna experiencia con eso, pero mi instinto es que no sería trivial. Creo que el complemento de autoproxy de Kevin Wright tiene un objective similar, pero no sería suficiente para mi problema (¿todavía?).

¿Tiene alguna otra idea que pueda funcionar? ¿De qué manera recomendarías? ¿Qué tipo de “desafíos” esperar?
¿O debería olvidarlo, porque no es posible con las restricciones actuales de Scala?

Intención detrás de mi problema: supongamos que tengo un flujo de trabajo empresarial, pero no es demasiado estricto. Algunos pasos tienen un orden fijo, pero otros no, pero al final todos tienen que hacerse (o algunos de ellos son necesarios para un procesamiento posterior).
Un ejemplo un poco más concreto: tengo una A, puedo agregar B y C a ella. No me importa qué se haga primero, pero al final necesitaré una A con B con C.

Comentario: No sé demasiado sobre Groovy, pero SO apareció esta pregunta y creo que es más o menos lo mismo que me gustaría, al menos concepcional.

Creo que esto es imposible de hacer estrictamente en tiempo de ejecución, porque los rasgos se combinan en tiempo de comstackción en nuevas clases de Java. Si mezcla un rasgo con una clase existente de forma anónima, puede ver, mirando los archivos de clase y usando javap, que Scalac crea una clase anónima y desmenuzada.

 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 scalac Mixin.scala; ls *.class

Foo.class Main$.class Spam$class.class Main$$anon$1.class Main.class Spam.class

Mientras javap Main\$\$anon\$1 regresa

 Compiled from "mixin.scala" public final class Main$$anon$1 extends Foo implements Spam{ public int eggs(); public Main$$anon$1(); } 

Como puede ver, scalac crea una nueva clase anónima que se carga en tiempo de ejecución; presumiblemente el método eggs en esta clase anónima crea una instancia de Spam$class y llama a eggs en él, pero no estoy del todo seguro.

Sin embargo , podemos hacer un truco muy hacky aquí:

 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("", 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 

Ya que necesita usar el comstackdor de Scala, AFAIK, esta es probablemente la solución más limpia que podría hacer para obtener esto. Es bastante lento, pero la memorización probablemente sea de gran ayuda.

Este enfoque es bastante ridículo, hacky y va contra el grano del lenguaje. Me imagino que podrían aparecer todo tipo de bichos raros; las personas que han usado Java por más tiempo que yo nos advertimos de la locura que implica jugar con los cargadores de clases.

Quería poder construir beans Scala en el contexto de mi aplicación Spring, pero también quería poder especificar los mixins que se incluirán en el bean construido:

 < ?xml version="1.0" encoding="UTF-8"?>        

La dificultad es que la función Class.forName no me permite especificar los mixins. Al final, extendí la solución hacky anterior a Scala 2.9.1. Entonces, aquí está en su totalidad sangrienta; incluyendo trozos de Spring.

 class ScalaBeanFactory(private val beanType: Class[_ <: AnyRef], private val mixinTypes: Seq[Class[_ <: AnyRef]]) { val loader = new DynamicClassLoader val clazz = loader.buildClass(beanType, mixinTypes) def getTypedObject[T] = getObject.asInstanceOf[T] def getObject = { clazz.newInstance() } def getObjectType = null def isSingleton = true 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: Class[_ <: AnyRef], vs: Seq[Class[_ <: AnyRef]]) = { val id = DynamicClassLoader.uniqueId val classDef = new StringBuilder classDef.append("class ").append(id) classDef.append(" extends ").append(t.getCanonicalName) vs.foreach(c => classDef.append(" with %s".format(c.getCanonicalName))) val settings = new Settings(null) settings.usejavacp.value = true val interpreter = new IMain(settings) interpreter.compileString(classDef.toString()) val r = interpreter.classLoader.getResourceAsStream(id) val o = new ByteArrayOutputStream val b = new Array[Byte](16384) Stream.continually(r.read(b)).takeWhile(_ > 0).foreach(o.write(b, 0, _)) val bytes = o.toByteArray defineClass(id, bytes, 0, bytes.length) } } 

El código aún no puede tratar con constructores con parámetros y no copia las anotaciones del constructor de la clase padre (¿debería hacer eso?). Sin embargo, nos proporciona un buen punto de partida que se puede utilizar en el espacio de nombres de Scala Spring. Por supuesto, no solo tome mi palabra, verifíquelo en una especificación Specs2:

 class ScalaBeanFactorySpec extends Specification { "getTypedObject mixes-in the specified traits" in { val f1 = new ScalaBeanFactory(classOf[Cat], Seq(classOf[Speaking], classOf[Eating])) val c1 = f1.getTypedObject[Cat with Eating with Speaking] c1.isInstanceOf[Cat with Eating with Speaking] must_==(true) c1.speak // in trait Speaking c1.eat // in trait Eating c1.meow // in class Cat } }