Dynamic mixin in Scala - is it possible?

后端 未结 2 1669
被撕碎了的回忆
被撕碎了的回忆 2020-11-27 04:23

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 wha

2条回答
  •  借酒劲吻你
    2020-11-27 05:05

    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("", 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.

提交回复
热议问题