8

我正在尝试编写一个简单的查询单子,但无法正确获取我的泛型类型注释。

我的第一次尝试如下(为了简洁而大大简化)

case class Person( val name: String )
abstract class Schema[T]    
object People extends Schema[Person]

case class Query[U <: Schema[T], T]( schema: U ) {      <---- Type signature
    def results: Seq[T] = ...
    def where( f: U => Operation ) = ...
}

class TypeText extends Application {
    val query = Query( People )                     <---- Type inference fails
}

编译器不喜欢这样,因为它无法推断“T”的类型。

错误:推断的类型参数 [People.type,Nothing] 不符合方法应用的类型参数边界 [U <: Schema[T],T]

在试验时,我发现使用视图边界可以按预期工作

case class Query[U <% Schema[T], T]( schema: U ) {

(注意使用视图绑定“<%”而不是类型绑定“<:”)

然而,在我对类型系统的有限理解中,由于我期待 Schema[T] 的实际子类(而不仅仅是可转换性),我会假设类型绑定“<:”是在这里使用的正确边界?

如果是这种情况,我错过了什么 - 在使用类型边界而不是视图边界时,如何给编译器足够的提示来正确推断 T?

4

4 回答 4

5

这不是一个完全令人满意的答案(至少对我而言),因为我不得不承认我无法准确说明推理在哪里以及为什么在这里失败。我对此只有一些模糊的直觉。该问题与编译器必须一次推断两个类型参数有关。至于为什么更改绑定到视图绑定的类型可以修复编译,我的理解是现在有两个参数列表,因此我们现在有两个连续的类型推断阶段,而不是一次两个推断。确实,以下内容:

case class Query[U <% Schema[T], T]( schema: U )

是相同的:

case class Query[U, T]( schema: U )( implicit conv: U => Schema[T] )

第一个参数列表驱动 的推理U,然后第二个参数列表(注意U现在已知)将驱动 的推理T

在表达式的情况下Query( People ),参数People将驱动类型推断器设置UPeople.type。然后,编译器将寻找从People.typeto的隐式转换Schema[T],以传入第二个参数列表。范围内唯一的一个是从People.type到的(微不足道的)转换Schema[Person],驱动推理器推断出T = Person

要在不使用视图绑定的情况下修复编译,可以将类型参数替换T为抽象类型:

case class Person( val name: String )
sealed trait Schema {
  type T
}
abstract class SchemaImpl[_T] extends Schema {
  type T = _T
}
object People extends SchemaImpl[Person]
case class Query[U <: Schema]( schema: U ) {
  def results: Seq[schema.T] = ???
}
class TypeText extends Application {
  val query = Query( People )
}

更新

@Aaron Novstrup 的:据我所知,您的答案不正确(更新更新:Aaron 的原始答案声称该Query声明等同于case class Query[U <: Schema[X], T](schema: U))。

case class Query[U <: Schema[X], T](schema: U)

甚至不编译。假设你的意思是

case class Query[U <: Schema[_], T](schema: U)

(它确实编译),很容易在 REPL 中检查它也不相同。

确实,以下编译得很好:

case class Query[U <: Schema[_], T](schema: U)
type MyQuery = Query[Schema[String], Int]

虽然,以下没有:

case class Query[U <: Schema[T], T](schema: U)
type MyQuery = Query[Schema[String], Int]

因此证明了差异。错误是:

<console>:10: error: type arguments [Schema[String],Int] do not conform to class Query's type parameter bounds [U <: Schema[T],T]
       type MyQuery = Query[Schema[String], Int]

这清楚地表明,第一次和第二次出现T表示相同的类型,并且我们确实在两个类型参数之间存在关系。

于 2013-04-30T13:36:48.297 回答
2

我有同样的问题。以下对我有用:

case class Query[U <: Schema[T], T]( schema: U with Schema[T] ) {
    ...
}
于 2015-02-20T07:19:08.900 回答
2

为了编码两个类型参数之间的关系,您可以使用类似

case class Query[U, T](schema: U)(implicit ev: U <:< Schema[T]) { ... }

有关更多信息,请参阅Scala 语言规范的§4.3 和 §4.4 。

于 2013-04-30T19:09:12.313 回答
1

我一直发现,在类/函数上使用两个类型标识符时,类型推断系统无法按预期工作,您必须像这样明确:

val query = Query[People.type, Person]( People )  

如果您将Query声明更改为:

case class Query[U <: Schema[_]( schema: U )

你可以这样做:

val query = Query( People )

但是,您将不知道所Schema提供的底层类型,并且无法正确实现该results功能。

于 2013-04-30T13:20:03.700 回答