我正在尝试编写一个简单的查询monad,但我无法正确理解泛型类型注释。
我的第一次尝试如下(为简洁而大大简化)
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]不符合方法apply的类型参数bounds [U&lt;:Schema [T],T]
在进行实验时,我发现使用视图边界可以按预期工作
case class Query[U <% Schema[T], T]( schema: U ) {
(注意使用视图绑定“&lt;%”而不是类型绑定“&lt;:”)
然而,由于我对类型系统的理解有限,因为我期待Schema [T]的实际子类(而不仅仅是可转换性),我假设类型绑定“&lt ;:”是正确使用的边界这里吗?
如果是这种情况,我错过了什么 - 在使用类型边界而不是视图边界时,如何给编译器足够的提示来正确推断T?
答案 0 :(得分: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
将驱动类型推断器将U
设置为People.type
。然后,编译器将查找从People.type
到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's:
据我所知,你的回答是不正确的(更新到更新:来自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
的第一次和第二次出现表示相同的类型,我们做在两个类型参数之间有关系。
答案 1 :(得分:2)
为了编码两个类型参数之间的关系,您可以使用类似
的内容case class Query[U, T](schema: U)(implicit ev: U <:< Schema[T]) { ... }
有关详细信息,请参阅Scala Language Spec的§4.3和§4.4。
答案 2 :(得分:2)
我遇到了同样的问题。以下对我有用:
case class Query[U <: Schema[T], T]( schema: U with Schema[T] ) {
...
}
答案 3 :(得分:1)
我总是发现,当在类/函数上使用两个类型标识符时,类型推理系统不能按预期工作,你必须像这样明确:
val query = Query[People.type, Person]( People )
如果您将Query
声明更改为:
case class Query[U <: Schema[_]( schema: U )
你可以这样做:
val query = Query( People )
但是你不会知道所提供的Schema
的基础类型,并且无法正确实现results
函数。