scala - pattern - swal.mixin is not a function
Mixin dinámico en Scala: ¿es posible? (2)
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 compilador 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 dinámico.
- 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)?
- 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):
- 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 compilador. No tengo ninguna experiencia con eso, pero mi instinto es que no sería trivial. Creo que el complemento de autoproxy Kevin Wright tiene un objetivo 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 compilació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("<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
Ya que necesita usar el compilador 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"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:scala="http://www.springframework.org/schema/scala"
xsi:schemaLocation=...>
<scala:bean class="org.cakesolutions.scala.services.UserService" >
<scala:with trait="org.cakesolutions.scala.services.Mixin1" />
<scala:with trait="org.cakesolutions.scala.services.Mixin2" />
<scala:property name="dependency" value="Injected" />
<scala:bean>
</beans>
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
}
}