2013-04-30 162 views
7

我正在尝试编写一个简单的查询monad,并且遇到问题,我的通用类型注释正确。Scala推断的类型参数 - 类型范围推断为'Nothing'

我第一次尝试去如下(大大简化了简洁)

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”的类型。

error: inferred type arguments [People.type,Nothing] do not conform to method apply's type parameter bounds [U <: Schema[T],T]

虽然尝试我发现,使用视图的边界,而不是按预期工作

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

(注意使用的角度约束“<%”,而不是约束型“<”)

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

如果是这样的话,我错过了什么 - 我怎么给编译器足够的提示,以便在使用类型边界而不是视图边界时正确推断T?

回答

2

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

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

参见4.3节和Scala Language Spec的§4.4获取更多信息。

+0

谢谢。我曾经看到过这种语法,并在想这是什么意思。 – 2013-05-01 06:36:24

+0

我不认为这个答案是正确的。请在我的答案底部查看我的回复(此评论不会为讨论提供足够的空间)。 – 2013-05-01 09:01:01

+0

@Régis你是对的。我误解了规范中的范围规则。这确实是一个推理问题,正如你在答案中所描述的那样。也就是说,使用隐式证据参数是对关系进行编码并支持所需推理的最简单方法。 – 2013-05-01 19:51:11

1

我总是发现,一类/函数使用两种类型标识符时,类型推理系统不能如预期般工作,你必须要明确的,像这样:

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

如果你改变了你Query声明如下:

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

你会能够做到这一点:

val query = Query(People) 

但是,您不知道提供的Schema的基础类型,并且将无法正确实现results函数。

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.typeSchema[T]的隐式转换,以传入第二个参数列表。范围中的唯一一个是从People.typeSchema[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) 
} 

UPDATE

@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第一和第二OCCURENCES表示相同类型的,我们有两种类型的参数之间的关系。

2

我有同样的问题。以下为我工作:

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