2010-07-15 8 views
39

Lo que me gustaría lograr es tener una implementación adecuada paraMixin dinámico en Scala - ¿es posible?

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

yo sepa qué es B, pero no se que A es (pero si B tiene un auto tipo entonces yo podría añadir un poco restricciones en A). El compilador scala está contento con la firma anterior, pero todavía no podía entender cómo se vería la implementación, si es posible.

Algunas opciones que vienen a la mente:

  • Usando la reflexión proxy/dinámico.
    • Caso más simple: A es una interfaz en el nivel de Java + Puedo crear una instancia B y no tiene un tipo de auto. Supongo que no sería demasiado difícil (a menos que me encuentre con algunos problemas desagradables e inesperados):
      crear un nuevo B (b), y también un proxy implementando A y B y usando un manejador de invocación delegando en a o b .
    • Si no se puede crear una instancia de B, aún así puedo 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, ¿qué pasa 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 una mezcla y devuelve B [A], a se puede acceder 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 sería necesario encuentre el nivel correcto de anidación para acceder a la funcionalidad necesaria, por lo que no lo considero una solución.
  • Implementación de un complemento de compilación. No tengo ninguna experiencia con eso, pero mi instinto es que no sería trivial. Creo que el complemento autoproxy de Kevin Wright tiene un objetivo un poco 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: Digamos 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 que se haga primero, pero al final necesitaré una A con B con C.

Comentario: No sé demasiado sobre Groovy pero SO apareció this question y supongo que es más o menos de lo que me gustaría, al menos concepcional.

+3

Otro enfoque (no pensado lo suficiente para saber si ello fuera viable): Envolver en un 'Dynamix [A, B]' (o anidado 'Dynamix [A, Dynamix [B, C]]') y desenvolver a través de implicits para descargar cognitivamente el código del cliente. Cada profundidad de anidación debe definirse (sin profundidad arbitraria). Desventaja: no impondrá restricciones de tipo propio ni permitirá la interacción entre tipos a través de los propios tipos. –

+0

+1 por el buen truco, pero tengo la sensación de que todavía correría al menos en un problema: cómo especificar que al final quiero una A con B con C o Dynamix [A, Dynamix [B, C] ] pero no me importa el orden de mezcla? También me alegraría con Dynamix [A, Dynamix [C, B]], y creo que no sería demasiado fácil encontrar una restricción (especialmente hay muchos rasgos que mezclar). Sin embargo, me gusta su comentario, gracias por ello. –

+0

posible duplicado de [Mezcla en un rasgo dinámicamente] (http://stackoverflow.com/questions/10373318/mixing-in-a-trait-dynamically) –

Respuesta

25

Creo que esto es imposible de hacer estrictamente en tiempo de ejecución, porque los rasgos se mezclan en tiempo de compilación en nuevas clases de Java.Si se mezcla un rasgo con una clase existente de forma anónima se puede ver, mirando a los archivos de clase y el uso de javap, que una clase de nombre destrozado anónima es creado por 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 vuelve

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

Mientras javap Main\$\$anon\$1 vuelve

Compiled from "mixin.scala" 

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

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

Sin embargo, podemos hacer un truco bastante 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 necesidad utilizar el compilador Scala, que yo sepa, esta es probablemente cerca de la solución más limpia que podría hacer para conseguir esta. 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.

+2

Estoy completamente de acuerdo con las desventajas, especialmente en contextos donde la carga de clases no es trivial y/o está habilitada para jugar.Para ser honesto, esperaba una solución más limpia, pero tampoco estaba seguro de si existía. Sin embargo, no hubo una mejor respuesta y probablemente funcionaría => aceptado. Muchas gracias por ello. –

3

que quería ser capaz de construir los granos de Scala en mi contexto de aplicación de la primavera, pero también quería ser capaz de especificar los mixins para ser incluido en el grano construida:

<?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 clase La función .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 hacer frente a los constructores con parámetros y no copiar anotaciones desde el constructor de la clase padre (en caso de que 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 acaba de tomar mi palabra para ella, verificarlo 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 
    } 

} 
Cuestiones relacionadas