40 votes

Dynamique mixin en Scala - est-il possible?

Ce que j'aimerais accomplir est d'avoir une bonne mise en œuvre de

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

Je peut savoir ce que B est, mais vous ne savez pas quelle est (mais si B a une auto de type alors je pourrais ajouter quelques contraintes sur Une). Le compilateur scala est heureux avec la signature ci-dessus, mais je ne pouvais pas encore comprendre comment la mise en œuvre pourrait ressembler - si c'est possible.

Certaines options qui est venu à mon esprit:

  • L'utilisation de la réflexion/dynamic proxy.
    • Cas plus simple: Un est une interface Java de niveau + je peux instancier B et il n'a pas d'auto type. Je suppose qu'il ne serait pas trop dur (à moins que j'ai des méchants, des problèmes inattendus):
      créer un nouveau B (b), et aussi un proxy de mise en œuvre à la fois A et B, et à l'aide d'une invocation gestionnaire de déléguer à l'un de a ou b.
    • Si B ne peut pas être instanciée, je pourrais toujours créer une sous-classe, et de faire comme il a été décrit ci-dessus. Si elle a également une auto de type I serait probablement besoin de quelques délégation ici et là, mais il peut encore travailler.
    • Mais que faire si a est Un type de béton et je ne peux pas trouver une bonne interface?
    • Aurais-je exécuter en plus de problèmes (par exemple, quelque chose lié à la linéarisation, ou de constructions spéciales aider Java interopérabilité)?
  • À l'aide d'un habillage à la place d'un mixin et retour le B[A], a est accessible à partir de b.
    Malheureusement, dans ce cas, l'appelant aurait besoin de savoir comment l'imbrication est fait, ce qui peut être assez gênant si le mélange dans/emballage est fait à plusieurs reprises (D[C[B[A]]]), car il faut trouver le bon niveau d'imbrication pour accéder à la fonctionnalité dont ils ont besoin, donc je ne le considérez pas comme une solution.
  • La mise en œuvre d'un compilateur plugin. Je n'ai aucune expérience avec elle, mais mon sentiment est qu'il ne serait pas anodin. Je pense que Kevin Wright autoproxy plugin a un peu le même objectif, mais il ne serait pas assez pour mon problème (encore?).

Avez-vous d'autres idées qui pourraient travailler? De quelle manière voulez-vous recommander? Quel genre de "défis" à attendre?
Ou dois-je l'oublier, car il n'est pas possible avec le Scala contraintes?

L'Intention derrière mon problème: Dire que j'ai un flux de travail d'entreprise, mais il n'est pas trop stricte. Certaines mesures ont fixé l'ordre, mais d'autres ne le font pas, mais à la fin, tout doit être fait (ou certains d'entre eux nécessaire pour la poursuite du traitement).
Un peu plus exemple concret: j'ai un A, que je peux ajouter, B et C pour elle. Je n'aime pas ce qui est fait en premier, mais à la fin je vais avoir besoin d'un A avec B à C.

Commentaire: je ne sais pas trop sur Groovy mais ALORS surgit cette question et je pense que c'est plus ou moins la même chose que ce que je voudrais, au moins conceptuelle.

26voto

stephenjudkins Points 517

Je crois que c'est impossible de faire strictement lors de l'exécution, car les traits sont mélangés au moment de la compilation dans de nouvelles classes Java. Si vous mélangez un trait avec une classe existante de manière anonyme, vous pouvez le voir, en regardant les classfiles et à l'aide de javap, anonyme, le nom mutilé de la classe est créée par 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 retours

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

Alors qu' javap Main\$\$anon\$1 retours

Compiled from "mixin.scala"

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

Comme vous pouvez le voir, scalac crée une nouvelle classe anonyme qui est chargé lors de l'exécution; sans doute la méthode de eggs dans cette classe anonyme crée une instance de Spam$class et des appels eggs , mais je ne suis pas complètement sûr.

Cependant, nous pouvons faire une jolie hacky astuce ici:

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

Depuis que vous avez besoin pour utiliser le compilateur Scala, autant que je sache, c'est probablement proche de la solution la plus propre que vous pouvez faire pour obtenir ce. C'est assez lent, mais memoization serait probablement aider grandement.

Cette approche est assez ridicule, hacky, et va à l'encontre de la langue. J'imagine toutes sortes de weirdo bugs pouvaient se glisser dans; des gens qui ont utilisé Java est plus que de me mettre en garde contre la folie qui vient avec déconner avec les chargeurs de classe.

3voto

Jan Machacek Points 31

Je voulais être en mesure de construire Scala haricots dans mon Printemps contexte de l'application, mais je voulais aussi être en mesure de préciser le mixin pour être inclus dans la construction du haricot:

<?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 difficulté est que la Classe.forName fonction ne me permet pas de spécifier le mixin. En fin de compte, j'ai étendu le ci-dessus hacky solution à la Scala 2.9.1. Donc, ici, il est dans son plein sanglant; y compris les bits de Printemps.

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)
  }

}

Le code ne peut pas encore traiter avec les constructeurs avec paramètres et ne copie pas les annotations de la mère du constructeur de la classe (faut-il le faire?). Cependant, il nous donne un bon point de départ qui est utilisable dans la scala de Printemps de l'espace de noms. Bien sûr, ne prenez pas mon mot pour lui, vérifier dans un Specs2 spécifications:

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
  }

}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X