4

Scala で、コンストラクター注入用の単純な依存性注入フレームワークを作成しています。DI されたオブジェクトは、必要なサービスを通常のパラメーターのようにコンストラクターに配置し、どの引数がコンテナーから取得され、どの引数がインスタンス化時にユーザーによって渡されるかを決定する型クラスを実装するという考え方です。

したがって、次のようになります。

trait Container {
  private singletons: Map[Class, AnyRef]
  def getSingleton[T: Manifest] =
    singletons(implicitly[Manifest[T]].erasure).asInstanceOf[T]
  ... methods for adding singletons, etc ...
}

class Foo(arg: String, svc: FooService) {
  ...
}

trait Constructor[T] { ??? }    

object FooConstructor extends Constructor[Foo] {
  def construct(arg: String)(implicit container: Container) =
    new Foo(arg, container.getSingleton[FooService])
}

基本的には、 というメソッドが必要です。このメソッドconstructを as として呼び出して、コンストラクターに渡してconstruct[Foo]("asd")の新しいインスタンスを取得し、ローカル コンテナーから取得してコンストラクターに渡します。アイデアは、型クラスのインスタンスを取得し、型保証された方法で、それが持つべき引数の数と型を知る必要があるということです。また、これは難しい部分です。引数の型を書き出す必要はありません。構築されるオブジェクトだけです。Foo"asd"FooServiceConstructorFoo

私はいくつかのことを試しました:

trait Constructor1[T, A] {
  def construct(arg: A): T
}

trait Constructor2[T, A1, A2] {
  def construct(arg1: A1, arg2: A2): T
}

def construct[T, A](arg1: A): T = implicitly[Constructor1[T, A]].construct(arg1)

...

ただし、型クラスのインスタンスを「呼び出す」ためには、引数の型を記述する必要があるように思われるため、このアプローチは機能しませんConstructor。これは厄介なボイラープレートです。

construct[Foo, String]("asd") // yuck!

型クラス (またはその他のもの) を使用して、型パラメーターを部分的に推論する方法はありますか? Fooインスタンス定義で定義されたコンストラクターパラメーターの型があるConstructorため、インスタンスを呼び出すことができればconstruct、正しい引数の型を呼び出して取得できるはずです。問題は、コンストラクターの型引数を指定せずにそのインスタンスを取得することです。私はこれについてさまざまなアイデアを試してみましたが、Scala のパワーとさまざまなトリックを使用して、記述して引数リストをタイプ セーフにする方法が必要であると感じています。construct[Foo]("asd")何か案は?

更新: Miles Sabin の優れた回答 + わずかな変更のおかげで、1 つの型パラメーターのみを必要とし、すべての異なる引数リストの長さに対して機能するメソッドを次に示します。これは、リフレクションのコストなしで、依存関係を簡単に接続するための非常に簡単な方法です。

trait Constructor1[T, A] { def construct(arg1: A)(implicit c: Container): T }
trait Constructor2[T, A, B] { def construct(arg1: A, arg2: B)(implicit c: Container): T }

implicit object FooConstructor extends Constructor1[Foo, String] {
  def construct(arg1: String)(implicit c: Container) = 
    new Foo(arg1, c.getSingleton[FooService])
}

implicit object BarConstructor extends Constructor2[Bar, String, Int] {
  def construct(arg1: String, arg2: Int)(implicit c: Container) = 
    new Bar(arg1, arg2, c.getSingleton[FooService])
}

class Construct[T] {
  def apply[A](arg1: A)(implicit ctor: Constructor1[T, A], container: Container) =
    ctor.construct(arg1)
  def apply[A, B](arg1: A, arg2: B)(implicit ctor: Constructor2[T, A, B], container: Container) =
    ctor.construct(arg1, arg2)
}

def construct[T] = new Construct[T]

construct[Foo]("asd")
construct[Bar]("asd", 123)
4

2 に答える 2

8

Scala での型パラメーターの推論は、全か無かの問題です。型パラメーター ブロックの型引数のいずれかを明示的に指定する場合は、それらすべてを指定する必要があります。したがって、型引数のセットの一部のみを指定する場合は、それらが別の型パラメーター ブロックに属するように調整する必要があります。

この場合の方法は、constructメソッドを 2 つの段階に分割することです。最初の段階では、明示的な型引数を取り、関数のような値を返します。もう 1 つは、関数のような値を、型を推測する引数に適用します。

これがどのように進むかです。

// Function-like type
class Construct1[T] {
  def apply[A](arg1: A)(implicit ctor : Constructor1[T, A]): T =
    ctor.construct(arg1)
}

def construct[T] = new Construct1[T]

呼び出しの結果はconstruct[Foo]type の値ですConstruct1[Foo]。これには、apply推論可能な型パラメーターを持つメソッドと、 と の両方によって型が決定される暗黙のパラメーターがありTますA。あなたが今したい呼び出しは、次のようになります。

construct[Foo].apply("asd")  // T explicit, A inferred as String

Scala のセマンティック シュガーリング ルールapplyがここに適用されます。つまり、これは次のように書き換えることができます。

construct[Foo]("asd")

これはまさにあなたが望む結果です。

于 2012-05-24T09:01:19.693 に答える
0
trait Construct[T, A] {
 def apply(arg: A): T
}

class Constructor[T]{
  def apply[A](arg : A)(implicit construct : Construct) = construct(arg)
}

object Constructor {
 def apply[T] = new Constructor[T]
}

Constructor[T]("arg")
于 2014-03-27T06:19:20.563 に答える