Scala中的dynamic混合 – 有可能吗?
我想要实现的是正确的实现
def dynamix[A, B](a: A): A with B
我可能知道B是什么,但不知道A是什么(但是如果B有一个自我types,那么我可以在A上添加一些约束)。 scala编译器对上面的签名很满意,但是我还不知道如何实现这个实现。
我想到的一些select:
- 使用reflection/dynamic代理。
- 最简单的情况:A是Java级别的接口+我可以实例化B,它没有自己的types。 我想这不会太难(除非遇到一些令人讨厌的意外问题):
创build一个新的B(b),同时也是一个实现A和B的代理,并使用调用处理程序委托给a或b。 - 如果B不能实例化,我仍然可以创build它的一个子类,并按照上面的描述进行操作。 如果它也有一个自我types,我可能会需要一些代表团,但它可能仍然有效。
- 但是,如果A是一个具体的types,我不能find一个适当的接口?
- 我会遇到更多的问题(例如与线性化有关的东西,还是有助于Java互操作性的特殊结构)?
- 最简单的情况:A是Java级别的接口+我可以实例化B,它没有自己的types。 我想这不会太难(除非遇到一些令人讨厌的意外问题):
- 使用一种包装而不是一个mixin并返回B [A],a可以从b访问。
不幸的是,在这种情况下,调用者需要知道如何完成嵌套,如果混入/包装多次完成(D [C [B [A]]),这可能是相当不方便的,因为它需要find正确的嵌套级别来访问所需的function,所以我不认为这是一个解决scheme。 - 实现一个编译器插件。 我没有经验,但我的直觉是,这不是微不足道的。 我认为凯文·赖特的autoproxy插件有一个类似的目标,但这还不足以解决我的问题(还没有?)。
你有任何其他想法可能工作? 你会推荐哪种方式? 期待什么样的“挑战”?
或者我应该忘记它,因为目前的Scala约束是不可能的?
我的问题背后的意图:说我有一个业务工作stream程,但不是太严格。 有些步骤有固定的顺序,但其他步骤没有,但最后所有的步骤都必须完成(或其中一些需要进一步处理)。
更具体的例子:我有一个A,我可以添加B和C到它。 我不在乎第一个做什么,但是最后我需要一个与B一起的A。
评论:我不太了解Groovy,但是这个问题突然出现了,我想这个问题或多或less和我想要的一样,至less是概念上的。
我相信这是不可能在运行时严格执行的,因为在编译时将特征混合到新的Java类中。 如果你将一个特性和一个现有的类匿名混合,你可以看到,看着类文件,并使用javap,一个匿名的,名称改变的类是由scalac创build的:
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
而javap Main\$\$anon\$1
返回
Compiled from "mixin.scala" public final class Main$$anon$1 extends Foo implements Spam{ public int eggs(); public Main$$anon$1(); }
正如你所看到的,scalac创build一个新的匿名类,在运行时加载; 大概这个匿名类的方法eggs
创build一个Spam$class
的实例,并打电话给eggs
,但我不完全确定。
但是 ,我们可以在这里做一个很好的诡计:
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
由于您需要使用Scala编译器AFAIK,因此您可能需要使用最简洁的解决scheme。 这是非常缓慢的,但memoization可能会大大帮助。
这种做法相当荒谬,哈克,并且违背了语言的五谷。 我想像着各种各样的奇怪臭虫可能会蔓延。 使用Java比我更久的人警告说伴随着类加载器的疯狂。
我希望能够在我的Spring应用程序上下文中构buildScala bean,但是我也希望能够指定要包含在构造bean中的mixins:
<?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>
困难在于Class.forName函数不允许我指定mixin。 最后,我把上面这个hacky的解决scheme扩展到了Scala 2.9.1。 所以,这里是充满血腥的; 包括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) } }
代码还不能处理带参数的构造函数,也不能从父类的构造函数中复制注释(应该这样做?)。 但是,它给了我们一个在scala Spring命名空间中可用的良好开端。 当然,不要只听我的话,在Specs2规范中进行validation:
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 } }