【问题标题】:Type mismatch on abstract type used in pattern matching模式匹配中使用的抽象类型的类型不匹配
【发布时间】:2019-02-27 23:40:52
【问题描述】:

此代码编译时出错:

def f1[T](e: T): T = e match {
  case i:Int => i
  case b:Boolean => b
}
// type mismatch;
// found   : i.type (with underlying type Int)
// required: T
// case i:Int => i ...

从类型检查的角度来看,这段实现 GADT 的代码看起来非常相似,但编译时没有错误:

sealed trait Expr[T]
case class IntExpr(i: Int) extends Expr[Int]
case class BoolExpr(b: Boolean) extends Expr[Boolean]

def eval[T](e: Expr[T]): T = e match {
  case IntExpr(i) => i
  case BoolExpr(b) => b
}

在模式匹配表达式的这两种情况下,我们知道 ibIntBoolean。为什么在第一个示例编译失败而在第二个示例编译成功?

【问题讨论】:

  • 我删除了我的答案(这是错误的)。我还发现,如果您从示例中删除显式返回类型,它会编译并运行。如果是C++ pre C++17 的情况(有编译时泛型,但没有编译时if),我会告诉我方法体对于任何类型T 都不正确,如@ 987654327@ 永远不会同时是 IntBoolean
  • @bobah 因为它推断出返回类型为 AnyVal
  • 你的函数只是def f1[T](e: T) = e。如果你想要一个解决方案,上下文会很有帮助。虽然也许你只是在寻找一个原因
  • @JoelBerkeley 这个问题主要是理论上的,而不是实际的。我正在尝试理解编译逻辑。
  • 这似乎取决于模式是值模式还是类型模式。这也失败了case class Expr[T](v: T); def eval[T](e: Expr[T]): T = e match { case Expr(i: Int) => i; case Expr(b: Boolean) => b }

标签: scala generics


【解决方案1】:

第一种情况是不合理的,因为您低估了 Scala 类型系统中类型的多样性。如果当我们采用case i:Int 分支时,我们知道TInt,或者至少是Int 的超类型,这将是有意义的。但它不一定是!例如。它可以是42.typetagged type

第二种情况不存在这样的问题,因为从IntExpr <: Expr[T],编译器确实知道T一定是Int

【讨论】:

    【解决方案2】:

    你要求你的函数返回一个类型T,然后你对IntBoolean进行模式匹配。 除了您的函数没有证据表明IntBoolean 也是T 类型:当您进行模式匹配时,you introduce the constraintInt <: TBoolean <: T。 您可以将返回类型T 替换为String 之类的固定类型并返回一个字符串,或者引入一个同时满足IntBoolean 的约束。

    //this compiles
    def f1[T](e: T ): String = e match {
      case _:Int => "integer"
      case _:Boolean => "boolean"
    }
    
    //this compiles too, but will return AnyVal
    def f1[T >: AnyVal](e: T ): T = e match {
       case i:Int => i
       case b:Boolean => b
    }
    

    基本上,您不能只动态返回任何类型 T,因为您需要在编译时证明您的函数类型已检出。

    您示例中的另一个函数通过在案例类IntExpr <: Expr[Int]BoolExpr <: Expr[Boolean] 中封装类型约束来避免该问题(注意Expr[_] 在我上面提到的约束中如何等效于T)。在编译时,T 在所有cases 中被正确识别(例如,在IntExpr知道它是一个Int

    【讨论】:

    • 他匹配e,类型为T。考虑问题中的第二个示例,case IntExpr(i) => i 可以编译,而case i: Int => i 不能。问题是有什么区别
    • “你的函数没有证据”——你的意思是编译器不能解决吗?毕竟,在匹配案例中,证据就在那里(假设您可能需要与case e => e 进行详尽的匹配)。用 Any 调用 f1 时不得不丢失类型检查将是一种耻辱。
    • Int 布尔值有一个共同的超类型,即AnyVal。编译器可以将T 推断为AnyVal
    • @joel 是的,问题出在函数的返回类型上。我试图为您的问题提供解决方案。
    • @fusion 如果它这样做了,只要你有类型问题,它就会回退到 AnyVal:我喜欢类型尽早发现错误,不确定这会有所帮助
    【解决方案3】:

    除了@Esardes 的回答之外,这还通过为T 定义一个类型来实现:

    scala> def f1[T >: AnyVal](e: T):T = e match {
         |   case i:Int => i
         |   case b:Boolean => b
         | }
    f1: [T >: AnyVal](e: T)T
    
    scala> f1(1)
    res3: AnyVal = 1
    
    scala> f1(true)
    res4: AnyVal = true
    

    【讨论】:

      猜你喜欢
      • 2016-09-04
      • 1970-01-01
      • 1970-01-01
      • 2015-10-02
      • 2014-02-05
      • 1970-01-01
      • 2012-01-02
      • 1970-01-01
      • 2015-02-28
      相关资源
      最近更新 更多