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

Posted

技术标签:

【中文标题】Scala中的动态混合 - 有可能吗?【英文标题】:Dynamic mixin in Scala - is it possible? 【发布时间】:2011-03-16 07:36:31 【问题描述】:

我想要实现的是有一个适当的实现

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

我可能知道 B 是什么,但不知道 A 是什么(但如果 B 具有 self 类型,那么我可以在 A 上添加一些约束)。 scala 编译器对上面的签名很满意,但我还不能弄清楚实现的样子——如果有可能的话。

我想到的一些选项:

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

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

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

评论:我对 Groovy 了解不多,但突然出现了this question,我猜它或多或少与我想要的相同,至少是概念性的。

【问题讨论】:

另一种方法(没有经过足够深思熟虑以知道是否可行):包裹在Dynamix[A,B](或嵌套Dynamix[A,Dynamix[B,C]])中并通过隐式展开以认知卸载客户端代码。必须定义每个嵌套深度(没有任意深度)。缺点:不会强制执行自类型约束或允许通过自类型进行跨类型交互。 +1 是个不错的技巧,但我觉得我仍然至少会遇到一个问题:如何指定最后我想要一个 A 和 B 和 C 或 Dynamix[A ,Dynamix[B,C]] 但我不在乎混合顺序?我也会对 Dynamix[A,Dynamix[C,B]] 感到满意,而且我认为提出这样的约束不会太容易(尤其是要混入许多特征)。不过,我喜欢你的评论,谢谢。 Mixing in a trait dynamically的可能重复 @om-nom-nom:最坏的情况,相反,但两年后答案发生了变化(例如,添加了反射)。 【参考方案1】:

我相信这不可能在运行时严格执行,因为特征在编译时被混合到新的 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,因此这可能是您可以获得的最简洁的解决方案。这很慢,但记忆可能会有很大帮助。

这种方法非常荒谬、老套,并且违背了语言的本质。我想各种奇怪的虫子都会潜入;使用 Java 的时间比我长的人警告说,搞乱类加载器会带来精神错乱。

【讨论】:

我完全同意这些缺点,尤其是在类加载不是微不足道和/或可以使用的情况下。老实说,我希望有一个更清洁的解决方案,但我也不确定它是否存在。尽管如此,没有更好的答案,它可能会起作用=>接受。非常感谢。【参考方案2】:

我希望能够在我的 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 函数不允许我指定 mixins。最后,我将上述 hacky 解决方案扩展到 Scala 2.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)
  


代码还不能处理带参数的构造函数,也不能从父类的构造函数中复制注解(应该这样做吗?)。但是,它为我们提供了一个很好的起点,可以在 scala Spring 命名空间中使用。当然,不要只相信我的话,在 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
  


【讨论】:

以上是关于Scala中的动态混合 - 有可能吗?的主要内容,如果未能解决你的问题,请参考以下文章

混合(Scala/Java)项目的单一文档?

在 Scala 中混合多个特征

c++有混合类型吗

在 JSF2 项目中混合 JSP 和 XHTML (Facelets) - 可能吗?

具有混合静态/动态内容的 UITableViewController 中的 NSRangeException

功能性OO混合语言的设计模式?