我正在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的方法,我可以将其称为construct[Foo]("asd")并获得Foo的新实例,并将"asd"传递给构造函数,并且FooService从本地容器获得并传递给构造函数。这个想法是,它应该为Constructor获取Foo类型类的实例,并以类型安全的方式知道其应具有的参数的数量和类型。另外,这也是困难的部分,我不想写出参数的类型-只是要构造的对象。

我已经尝试了几件事:

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类型的类实例,我们需要编写参数的类型,即有很多讨厌的样板:

我们在Foo实例定义中定义了Constructor的构造函数参数类型,因此,如果我们可以召唤该实例,我们应该能够调用construct并获得正确的参数类型。问题在于获取该实例而不必指定构造函数类型参数。我为此考虑了很多不同的想法,并且我觉得利用Scala的强大功能和技巧,必须有一种方法可以编写construct[Foo]("asd")并使参数列表类型安全。有什么想法吗?

更新:感谢Miles Sabin的出色回答+稍加修改,这是一种只需要一个类型参数并适用于所有不同参数列表长度的方法。这是一种轻松连接依赖关系的简单方法,而无需进行反思:

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


#1 楼

Scala中的类型参数推断是一事无成:如果您为类型参数块显式提供任何类型参数,则必须全部提供。因此,如果只想提供一组类型参数中的一部分,则必须安排它们属于单独的类型参数块。分为两个阶段:第一个阶段,该阶段采用显式类型参数并返回类似函数的值;第二种方法是将函数式值应用于您要为其推断类型的参数。

这是可能的方法,

>
调用construct的结果是类型construct[Foo]的值。它具有一个Construct1[Foo]方法,该方法带有可以推断的类型参数,以及一个隐式参数,其类型由applyT共同确定。现在,您要进行的调用看起来像是

// 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]


Scala关于A的语义糖化规则在这里适用,这意味着可以将其重写为/>
construct[Foo].apply("asd")  // T explicit, A inferred as String


这正是您想要的结果。

评论


感谢您的答复,我有点希望您能够看到它……对不起,这个问题太长了!不幸的是,这还不能完全解决问题,因为我不能使不同的arg列表长度的重载都起作用。使用这种方法,我不得不说Construct1 [Foo](“ asd”),construct2 [Bar](“ asd”,123)等。我尝试添加一个隐式证据参数来区分结构的重载(construct [T](隐式ev:构造函数[T,_,_])等),但它似乎不起作用...

– lvilnis
2012年5月24日14:06



我解决了!诀窍是没有一个Construct1 [T]对象,而只有一个Construct [T]对象,该对象具有所有“应用”方法,每个参数列表长度一个。我会更新我的问题。谢谢你的帮助!

– lvilnis
2012年5月24日14:11

#2 楼

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


评论


这基本上与接受的答案相同(早了两年),除了这个答案甚至没有编译并且没有解释。

–特拉维斯·布朗(Travis Brown)
16年4月9日在16:27