代码之家  ›  专栏  ›  技术社区  ›  Sandor Murakozi

Scala中的动态混合-可能吗?

  •  40
  • Sandor Murakozi  · 技术社区  · 14 年前

    我想实现的是为

    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是一个具体的类型,而我找不到合适的接口怎么办?
    • 使用一种包装代替mixin并返回B[a],可以从B访问a。
      不幸的是,在这种情况下,调用者需要知道嵌套是如何完成的,如果混合入/包装要做几次(D[C[B[A]]),这会非常不方便,因为它需要找到合适的嵌套级别来访问所需的功能,所以我不认为这是一个解决方案。
    • 实现编译器插件。我对它没有任何经验,但我的直觉是,它不会是微不足道的。我想凯文·赖特 autoproxy

    你还有其他可行的办法吗?你推荐哪条路?期待什么样的“挑战”?
    或者我应该忘记它,因为它不可能与当前的Scala约束?

    我的问题背后的意图: 假设我有一个业务工作流程,但它不是太严格。一些步骤有固定的顺序,但其他步骤没有,但最后必须完成所有步骤(或者需要进一步处理其中的一些步骤)。
    一个更具体的例子:我有一个A,我可以加上B和C。我不在乎先做哪一个,但最后我需要A加B加C。

    评论:我对Groovy了解不多,但却突然出现了 this question 我想这和我想要的差不多,至少在概念上是一样的。

    2 回复  |  直到 7 年前
        1
  •  26
  •   stephenjudkins    14 年前

    我相信这在运行时是不可能严格做到的,因为特性在编译时被混合到新的Java类中。如果匿名地将一个trait与现有类混合,您可以看到,通过查看类文件并使用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 还有电话 但我不太确定。

    ,我们可以在这里做一个相当恶作剧的把戏:

    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,这可能是最干净的解决方案。虽然速度很慢,但回忆录可能会大有帮助。

        2
  •  3
  •   Jan Machacek    13 年前

    我希望能够在我的Spring应用程序上下文中构造Scala bean,但我也希望能够指定要包含在构造的bean中的mixin:

    <?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。最后,我将上述黑客解决方案扩展到Scala2.9.1。所以,在这里它是在它的充分血淋淋;包括一些弹簧。

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

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