首页 文章

Scala推断类型参数 - 类型边界推断为'Nothing'

提问于
浏览
7

我正在尝试编写一个简单的查询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 <:Schema [T],T]

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

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

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

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

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

4 回答

  • 5

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

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

    有关详细信息,请参阅Scala Language Spec的§4.3和§4.4 .

  • 1

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

    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.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 )
    }
    

    UPDATE

    @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 的第一次和第二次出现表示相同的类型,并且我们在两个类型参数之间存在关系 .

  • 2

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

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

    我总是发现,当在类/函数上使用两个类型标识符时,类型推断系统不能按预期工作,你必须像这样显式:

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

    如果您将 Query 声明更改为:

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

    你可以这样做:

    val query = Query( People )
    

    但是你不会知道所提供的 Schema 的基础类型,并且无法正确实现 results 函数 .

相关问题