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.
- 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):
- 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?).
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.
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.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
}
}
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