【问题标题】:Scala inferred type arguments - Type bounds inferring to 'Nothing'Scala 推断类型参数 - 推断为“无”的类型边界
【发布时间】:2013-04-30 02:33:01
【问题描述】:

我正在尝试编写一个简单的查询 monad,但无法让我的 generic 类型注释正确。

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

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>

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

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

(注意使用视图绑定“

但是,在我对类型系统的有限理解中,由于我期待 Schema[T] 的实际子类(而不仅仅是可转换性),我会假设类型绑定“<:>

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

【问题讨论】:

    标签: scala generics type-inference type-bounds


    【解决方案1】:

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

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

    等同于:

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

    第一个参数列表驱动@9​​87654323@的推理,然后第二个(注意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 )
    }
    

    更新

    @Aaron Novstrup 的: 据我所知,您的答案不正确(更新更新:Aaron 的原始答案声称Query 声明等同于case class Query[U &lt;: 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, T](schema: U)(implicit ev: U <:< Schema[T]) { ... }
      

      有关详细信息,请参阅Scala Language Spec 的第 4.3 节和第 4.4 节。

      【讨论】:

      • 谢谢。我看过这种语法,想知道它是什么意思。
      • 我认为这个答案不正确。请在我的答案底部查看我的回复(此评论不会为讨论提供足够的空间)。
      • @Régis 你是对的。我误解了规范中的范围规则。正如您在回答中所描述的,这确实是一个推理问题。也就是说,使用隐式证据参数是对关系进行编码并支持所需推理的最简单方法。
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

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

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

        如果您将 Query 声明更改为:

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

        你可以这样做:

        val query = Query( People )
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-03-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多