Dynamiczny mixin w Scali - czy to możliwe?

To, co chciałbym osiągnąć, to mieć odpowiednie wdrożenie dla

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

Może wiem, co to jest B, ale nie wiem, co to jest A (ale jeśli B mA Typ self, to mogę dodać pewne ograniczenia Na A). Kompilator Scali jest zadowolony z powyższego podpisu, ale nie mogłem jeszcze dowiedzieć się, jak implementacja będzie wyglądać - jeśli jest to w ogóle możliwe.

Niektóre opcje, które przyszły mi do głowy:

  • używanie proxy refleksyjnego / dynamicznego.
    • najprostszy przypadek: a jest interfejsem na poziomie Java + mogę utworzyć instancję B i nie ma ona typu self. Myślę, że nie byłoby to zbyt trudne (chyba, że napotkam jakieś paskudne, nieoczekiwane problemy):
      tworzenie nowego B (b), a także proxy implementujące ZARÓWNO a jak i B i używające obsługi wywołania delegującego do a lub b.
    • Jeśli nie można utworzyć instancji B, nadal mogę utworzyć jej podklasę i zrobić to, co zostało opisane powyżej. Jeśli ma również własny typ, prawdopodobnie potrzebuję delegacji tu i tam, ale może nadal praca.
    • ale co jeśli A jest konkretnym typem i nie mogę znaleźć odpowiedniego interfejsu dla niego?
    • czy napotkałbym więcej problemów (np. coś związanego z linearyzacją, czy specjalne konstrukcje wspomagające interoperacyjność Javy)?
  • W przeciwieństwie do innych formatów, nie można ich używać do łączenia z innymi formatami.]} Niestety w tym przypadku rozmówca musiałby wiedzieć, jak odbywa się zagnieżdżanie, co może być dość niewygodne, jeśli mieszanie w/owijanie jest zrobione kilka razy (D [C [B [A]]]), ponieważ musiałby znaleźć odpowiedni poziom zagnieżdżania, aby uzyskać dostęp do potrzebnej funkcjonalności, więc nie uważam tego za rozwiązanie.
  • Implementowanie wtyczki kompilatora. Nie mam z tym żadnego doświadczenia, ale mam przeczucie, że nie byłoby to trywialne. Myślę, że wtyczka autoproxy Kevina Wrighta ma nieco podobny cel, ale nie wystarczyłaby na mój problem (jeszcze?).
Masz jakieś inne pomysły, które mogą zadziałać? Którą drogę byś polecił? Jakich "wyzwań" można się spodziewać?
A może powinienem o tym zapomnieć, ponieważ nie jest to możliwe przy obecnych ograniczeniach Scali?

Intencja stojąca za moim problemem: Powiedzmy, że mam biznesowy przepływ pracy, ale nie jest zbyt surowy. Niektóre kroki mają ustaloną kolejność, ale inne nie, ale na końcu wszystkie z nich muszą być wykonane (lub niektóre z nich wymagane do dalszego przetwarzania).
Trochę bardziej konkretny przykład: mam A, mogę do niego dodać B i C. Nie obchodzi mnie, co jest zrobione najpierw, ale na koniec będę potrzebował a z B z C.

Komentarz: Nie wiem za dużo o Groovy, ale to pytanie i wydaje mi się, że jest mniej więcej takie samo jak to, co bym chciał, przynajmniej koncepcyjne.

Author: Community, 2010-07-15

2 answers

Uważam, że jest to niemożliwe do wykonania wyłącznie w czasie wykonywania, ponieważ cechy są mieszane w czasie kompilacji do nowych klas Java. Jeśli połączysz cechę z istniejącą klasą anonimowo, zobaczysz, patrząc na pliki klas i używając javap, że anonimowa, wymazana nazwą klasa jest tworzona przez 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 returns

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

While javap Main\$\$anon\$1 zwraca

Compiled from "mixin.scala"

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

Jak widać, scalac tworzy nową anonimową klasę, która jest ładowana w czasie wykonywania; przypuszczalnie metoda eggs w tej anonimowej klasie tworzy instancję Spam$class i wywołuje eggs na niej, ale nie jestem do końca pewien.

Jednak , możemy zrobić dość hacky trick tutaj:

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

Ponieważ potrzebujesz aby użyć kompilatora Scali, AFAIK, jest to prawdopodobnie najczystsze rozwiązanie, jakie możesz zrobić, aby to uzyskać. Jest dość powolny, ale memoizacja prawdopodobnie bardzo by pomogła.

[[9]}to podejście jest dość śmieszne, hacky, i idzie na przekór ziarna język. Wyobrażam sobie, że mogą wkradać się wszelkiego rodzaju dziwne błędy; ludzie, którzy używali Javy dłużej niż ja, ostrzegają przed szaleństwem, które wiąże się z wygłupianiem się z classloaderami.
 26
Author: stephenjudkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-16 23:15:39

Chciałem być w stanie konstruować fasolę Scala w moim wiosennym kontekście aplikacji, ale chciałem również być w stanie określić mixiny, które mają być zawarte w constructed bean:

<?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>

Trudność jest ta klasa.funkcja forName nie pozwala mi określić mixinów. W końcu rozszerzyłem powyższe rozwiązanie hacky do Scali 2.9.1. Tak więc, tutaj jest w pełnej krwawości; w tym fragmenty Wiosny.

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

}

Kod nie może jeszcze zajmować się konstruktorami z parametrami i nie skopiuj adnotacje z konstruktora klasy nadrzędnej (czy powinien to zrobić?). Jednak daje nam dobry punkt wyjścia, który jest użyteczny w przestrzeni nazw Scala Spring. Oczywiście, nie wierz mi na słowo, zweryfikuj to w specyfikacji 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
  }

}
 3
Author: Jan Machacek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-23 13:14:09