用隐式模拟部分类型参数推断? [英] Simulate partial type parameter inference with implicits?

查看:32
本文介绍了用隐式模拟部分类型参数推断?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在 Scala 中制作一个简单的依赖注入框架,用于构造函数注入.这个想法是,DI 对象将它们所需的服务像常规参数一样放在它们的构造函数中,并实现一个类型类,以确定它们的哪些参数是从容器中获取的,哪些是由用户在实例化时传递的.

I'm making a simple dependency injection framework, for constructor injection, in Scala. The idea is that objects that are DI'd put their required services in their constructor like regular parameters, and implement a typeclass that determines which of their arguments are taken from the container and which are passed by the user on instantiation.

所以,它应该看起来像:

So, it should look something like:

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 从本地容器中获取并传入构造函数.这个想法是它应该获取 FooConstructor 类型类的实例,并以类型安全的方式知道它应该具有的参数的数量和类型.此外,这是困难的部分,我不想写出参数的类型 - 只是要构造的对象.

Now basically I'd like to be able to have a method called construct, which I can call as construct[Foo]("asd") and get a new instance of Foo with "asd" passed in to the constructor, and FooService gotten from the local container and passed in to the constructor. The idea is that it should grab the instance of the Constructor type class for Foo and in a typesafe way, know the number and types of arguments it should have. Also, and this is the hard part, I don't want to have to write out the types of the arguments - just the object to be constructed.

我尝试了几件事:

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

This approach doesn't work though because it seems like in order to "summon" the Constructor type class instance, we need to write the types of the arguments, which is a lot of nasty boilerplate:

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

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

Is there a way to use type classes (or anything else) to sort of partially infer the type parameters? We have the types of the constructor parameters for Foo defined in the Constructor instance definition, so if we can summon the instance we should be able to just call construct and get the right argument types. The issue is getting that instance without having to specify the constructor type arguments. I've played around with a bunch of different ideas for this, and I feel like with Scala's power and bag of tricks there just has to be a way I can write construct[Foo]("asd") and have the argument list be type safe. Any ideas?

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

UPDATE: Thanks to Miles Sabin's excellent answer + a slight modification, here's a method that only requires one type parameter and works for all different argument list lengths. This is a pretty simple way to painlessly wire up dependencies, without the cost of reflection:

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 中的类型参数推断是一个全有或全无的问题:如果您为类型参数块显式提供任何类型参数,那么您必须全部提供它们.因此,如果您只想提供一组类型参数中的一部分,您必须将它们安排为属于单独的类型参数块.

Type parameter inference in Scala is an all or nothing affair: if you explicitly supply any of the type arguments for a type parameter block then you must supply them all. Consequently, if you want to supply only some of a set of type arguments you must arrange for them to belong to separate type parameter blocks.

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

The way to do that in this case is to split the construct method into two stages: the first, which takes an explicit type argument and returns a function-like value; and a second, which applies the function-like value to the arguments for which you want the types to be inferred.

可能会这样,

// 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 决定.您现在要进行的调用看起来像,

The result of invoking construct[Foo] is a value of type Construct1[Foo]. This has an apply method with a type parameter, which can be inferred, and an implicit parameter whose type is determined by both T and A. The invocation you want to make now looks like,

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

Scala 围绕 apply 的语义加糖规则在这里适用,这意味着这可以重写为,

Scala's semantic sugaring rules around apply applies here which mean that this can be rewritten as,

construct[Foo]("asd")

这正是您想要的结果.

这篇关于用隐式模拟部分类型参数推断?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆