【问题标题】:Simulate partial type parameter inference with implicits?用隐式模拟部分类型参数推断?
【发布时间】:2012-05-23 19:01:57
【问题描述】:

我正在 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" 传递给构造函数,然后@ 987654326@ 从本地容器中获取并传递给构造函数。这个想法是它应该为Foo 获取Constructor 类型类的实例,并以一种类型安全的方式,知道它应该具有的参数的数量和类型。另外,这是最难的部分,我不想写出参数的类型——只写要构造的对象。

我尝试了几件事:

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!

有没有办法使用类型类(或其他任何东西)来部分推断类型参数?我们在Constructor 实例定义中定义了Foo 的构造函数参数类型,因此如果我们可以调用实例,我们应该能够调用construct 并获得正确的参数类型。问题是无需指定构造函数类型参数即可获取该实例。我为此尝试了很多不同的想法,我觉得凭借 Scala 的强大功能和一系列技巧必须成为我可以编写 construct[Foo]("asd") 并拥有参数列表的一种方式类型安全。有什么想法吗?

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

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)

【问题讨论】:

    标签: scala


    【解决方案1】:

    Scala 中的类型参数推断是全有或全无的事情:如果您为类型参数块显式提供任何类型参数,那么您必须全部提供它们。因此,如果您只想提供一组类型参数中的一部分,则必须安排它们属于单独的类型参数块。

    在这种情况下,这样做的方法是将construct 方法分成两个阶段:第一个阶段,它接受一个显式类型参数并返回一个类似函数的值;第二个,它将类似函数的值应用于您希望为其推断类型的参数。

    事情可能会这样,

    // 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] 的结果是Construct1[Foo] 类型的值。这有一个apply 方法,它带有一个可以推断的类型参数,以及一个类型由TA 确定的隐式参数。您现在要进行的调用看起来像,

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

    Scala 围绕apply 的语义糖化规则适用于此,这意味着可以将其重写为,

    construct[Foo]("asd")
    

    这正是你想要的结果。

    【讨论】:

    • 感谢您的回复,我有点希望您能看一下...抱歉问题太长了!不幸的是,这还不能完全解决问题,因为我无法使不同 arg 列表长度的重载工作。使用这种方法,我不得不说construct1[Foo]("asd")construct2[Bar]("asd", 123)等。我尝试添加一个隐式证据参数来区分constructconstruct[T](implicit ev: Constructor[T,_,_])等)的重载,但它没有似乎正在工作......
    • 我解决了!诀窍是没有Construct1[T] 对象,而只有一个Construct[T] 对象,它具有所有“应用”方法,每个参数列表长度一个。我会更新我的问题。感谢您的帮助!
    【解决方案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")
    

    【讨论】:

    • 这与公认的答案基本相同(两年前),除了这个甚至没有编译并且没有解释。
    猜你喜欢
    • 2016-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-06
    • 1970-01-01
    • 2017-05-31
    • 1970-01-01
    相关资源
    最近更新 更多