Scala中的动态mixin-可能吗? [英] Dynamic mixin in Scala - is it possible?

查看:91
本文介绍了Scala中的动态mixin-可能吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想实现的目标是针对

进行适当的实现

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

我可能知道B是什么,但不知道A是什么(但是如果B具有自类型,那么我可以对A添加一些约束). Scala编译器对上面的签名感到满意,但是我仍然无法弄清楚实现的样子-如果有可能的话.

我想到了一些选择:

  • 使用反射/动态代理.
    • 最简单的情况:A是Java级别的接口+我可以实例化B并且它没有自类型.我想这不会太难(除非我遇到一些令人讨厌的意外问题):
      创建一个新的B(b),以及一个同时实现A和B并使用委派给a或b的调用处理程序的代理.
    • 如果无法实例化B,我仍然可以创建它的子类,并按照上面的描述进行操作.如果它也具有自我类型,则可能需要在这里和那里进行一些委派,但是它仍然可以工作.
    • 但是如果A是一个具体类型,而我却找不到合适的接口怎么办?
    • 我会遇到更多问题(例如与线性化相关的问题,或有助于Java互操作性的特殊构造)吗?
  • 使用一种包装而不是mixin并返回B [A],可以从b访问a.
    不幸的是,在这种情况下,调用方将需要知道嵌套的完成方式,如果多次进行混合/包装(D [C [B [A]]])可能会很不方便,因为它将需要查找嵌套.正确级别的嵌套以访问所需的功能,因此我不认为这是一种解决方案.
  • 实现编译器插件.我对它没有零经验,但是我的直觉是那将是微不足道的.我认为Kevin Wright的 autoproxy 插件的目标有点相似,但是可以还不够解决我的问题(还吗?).

您还有其他可行的想法吗?您会推荐哪种方式?期望什么样的挑战"?
还是我应该忘记它,因为当前的Scala约束是不可能的?

问题背后的意图: 假设我有一个业务工作流程,但这不是太严格.有些步骤具有固定的顺序,而其他步骤则没有固定顺序,但是最后必须完成所有步骤(或某些步骤需要进一步处理).
再举一个具体的例子:我有一个A,可以在其中添加B和C.我不在乎先做什么,但最后我需要一个A和B以及C.

评论:我对Groovy不太了解,但SO弹出此问题,我想至少在概念上与我想要的差不多.

我相信严格在运行时不可能做到这一点,因为特质在编译时混入了新的Java类中.如果将特征与现有类匿名混合使用,您可以看到,通过查看类文件并使用javap,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返回

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创建了一个新的匿名类,该匿名类在运行时加载;大概这个匿名类中的方法eggs创建了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,因此这可能是您可以采用的最干净的解决方案.速度很慢,但是记忆可能会大有帮助.

这种方法非常荒谬,hacky,并且与语言的本质背道而驰.我想象各种怪异的虫子都会潜入其中.使用Java的时间长于我的人警告说,搞乱类加载器会带来精神错乱.

What I'd like to achieve is having a proper implementation for

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

I may know what B is, but don't know what A is (but if B has a self type then I could add some constraints on A). The scala compiler is happy with the above signature, but I could not yet figure out how the implementation would look like - if it is possible at all.

Some options that came to my mind:

  • Using reflection/dynamic proxy.
    • Simplest case: A is an interface on Java level + I can instantiate B and it has no self type. I guess it would not be too hard (unless I run into some nasty, unexpected problems):
      create a new B (b), and also a proxy implementing both A and B and using an invocation handler delegating to either a or b.
    • If B can not be instantiated I could still create a subclass of it, and do as it was described above. If it also has a self type I would probably need some delegation here and there, but it may still work.
    • But what if A is a concrete type and I can't find a proper interface for it?
    • Would I run into more problems (e.g. something related to linearization, or special constructs helping Java interoperability)?
  • Using a kind of wrapping instead of a mixin and return B[A], a is accessible from b.
    Unfortunately in this case the caller would need to know how the nesting is done, which could be quite inconvenient if the mixing in/wrapping is done several times (D[C[B[A]]]) as it would need to find the right level of nesting to access the needed functionality, so I don't consider it a solution.
  • Implementing a compiler plugin. I have zero experience with it but my gut feeling is that it would not be trivial. I think Kevin Wright's autoproxy plugin has a bit similar goal, but it would not be enough for my problem (yet?).

Do you have any other ideas that might work? Which way would you recommend? What kind of "challenges" to expect?
Or should I forget it, because it is not possible with the current Scala constraints?

Intention behind my problem: Say I have a business workflow, but it's not too strict. Some steps have fixed order, but others do not, but at the end all of them has to be done (or some of them required for further processing).
A bit more concrete example: I have an A, I can add B and C to it. I don't care which is done first, but at the end I'll need an A with B with C.

Comment: I don't know too much about Groovy but SO popped up this question and I guess it's more or less the same as what I'd like, at least conceptional.

解决方案

I believe this is impossible to do strictly at runtime, because traits are mixed in at compile-time into new Java classes. If you mix a trait with an existing class anonymously you can see, looking at the classfiles and using javap, that an anonymous, name-mangled class is created by 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 returns

Compiled from "mixin.scala"

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

As you can see, scalac creates a new anonymous class that is loaded at runtime; presumably the method eggs in this anonymous class creates an instance of Spam$class and calls eggs on it, but I'm not completely sure.

However, we can do a pretty hacky trick here:

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

Since you need to use the Scala compiler, AFAIK, this is probably close to the cleanest solution you could do to get this. It's quite slow, but memoization would probably help greatly.

This approach is pretty ridiculous, hacky, and goes against the grain of the language. I imagine all sorts of weirdo bugs could creep in; people who have used Java longer than me warn of the insanity that comes with messing around with classloaders.

这篇关于Scala中的动态mixin-可能吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆