40

私が達成したいのは、適切な実装を行うことです

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 にアクセスできます。
    残念なことに、この場合、呼び出し元はネスティングがどのように行われるかを知る必要があります。必要な機能にアクセスするための適切なレベルのネストであるため、解決策とは考えていません。
  • コンパイラ プラグインの実装。私はそれを経験したことはありませんが、私の直感は、それは簡単ではないということです. Kevin Wright のautoproxyプラグインにも少し似たような目的があると思いますが、私の問題には十分ではありません (まだ?)。

他に有効なアイデアはありますか?どの方法をお勧めしますか? どのような「挑戦」が待っているのでしょうか。
それとも、現在の Scala 制約では不可能なため、忘れるべきでしょうか?

問題の背後にある意図: ビジネス ワークフローがあるとしますが、厳密すぎません。順序が固定されているステップもあれば、固定されていないステップもありますが、最後にすべてのステップを実行する必要があります (または、さらに処理が必要なステップもあります)。
もう少し具体的な例: A があり、それに B と C を追加できます。どちらが先かは気にしませんが、最後には A と B と C が必要になります。

コメント: 私は Groovy についてあまり知りませんが、SO がこの質問をポップアップしました。

4

2 に答える 2

26

コンパイル時に特性が新しい 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コンパイラを使用する必要があるため、これはおそらくこれを実現するために実行できる最もクリーンなソリューションに近いでしょう。かなり遅いですが、メモ化はおそらく大いに役立つでしょう。

このアプローチはかなりばかげており、ハックであり、言語の粒度に反します。あらゆる種類の奇妙なバグが忍び寄る可能性があると思います。私よりも長く Java を使用している人々は、クラスローダーをいじることに伴う狂気について警告しています。

于 2010-07-16T23:15:39.720 に答える
3

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 関数ではミックスインを指定できないことです。最後に、上記のハッキーなソリューションを 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
  }

}
于 2012-01-23T13:14:09.073 に答える