【问题标题】:how does scala's type check work in this case? [closed]在这种情况下,scala 的类型检查如何工作? [关闭]
【发布时间】:2020-06-18 06:43:14
【问题描述】:
// Start writing your ScalaFiddle code here
sealed trait DSL[A]{
  // def run(): A ={
  //   this match {
  //     case GetLength(something) =>
  //       something.length
  //     case ShowResult(number) =>
  //       s"the length is $number"
  //   }
  // }
}

case class GetLength(something: String) extends DSL[Int]
case class ShowResult(number: Int) extends DSL[String]

def run[A](fa:DSL[A]): A ={
  fa match {
    case GetLength(something) =>
      something.length
    case ShowResult(number) =>
      s"the length is $number"
  }
}

val dslGetLength = GetLength("123456789")
val length = run(dslGetLength)
val dslShowResult = ShowResult(length)
println(run(dslShowResult))
// print: the length is 9

scalafiddle here

  • 为什么run 函数不能在DSL[A] trait 中编译,而是在外部工作?
  • 在这种情况下类型推断如何工作?

【问题讨论】:

    标签: scala generics pattern-matching type-inference


    【解决方案1】:

    这是一个广义抽象数据类型的例子。

    当你有一个DSL[A] 和返回A 的函数时,编译器可以证明:

    • case GetLengthA=Int,所以你可以在那里返回Int
    • for case ShowResult A=String 所以你可以返回String

    然而,众所周知,Scala 2 对 GADT 没有完美的支持,所以有时编译器会失败,即使它应该可以工作。我想一些编译器开发人员可以找出确切的情况,但有趣的是,它可以解决:

    sealed trait DSL[A]{
      def run(): A = DSL.run(this)
    }
    object DSL {
      def run[A](fa:DSL[A]): A ={
        fa match {
          case GetLength(something) =>
            something.length
          case ShowResult(number) =>
            s"the length is $number"
        }
      }
    }
    
    case class GetLength(something: String) extends DSL[Int]
    case class ShowResult(number: Int) extends DSL[String]
    

    我的猜测是泛型方法中的模式匹配是编译器中的一种特殊情况,当A 被修复时不会触发。我认为,因为以下代码也有效:

    sealed trait DSL[A]{
      def run(): A = runMe(this)
      private def runMe[B](dsl: DSL[B]): B = {
        dsl match {
          case GetLength(something) =>
            something.length
          case ShowResult(number) =>
            s"the length is $number"
        }
      }
    }
    

    而这也失败了:

    sealed trait DSL[A]{
      def run(): A = {
        val fa: DSL[A] = this // make sure error is not related to special treatment of "this", this.type, etc
        fa match {
          case GetLength(something) =>
            something.length
          case ShowResult(number) =>
            s"the length is $number"
        }
      }
    }
    
    cmd4.sc:5: constructor cannot be instantiated to expected type;
     found   : ammonite.$sess.cmd4.GetLength
     required: ammonite.$sess.cmd4.DSL[A]
          case GetLength(something) =>
               ^
    cmd4.sc:7: constructor cannot be instantiated to expected type;
     found   : ammonite.$sess.cmd4.ShowResult
     required: ammonite.$sess.cmd4.DSL[A]
          case ShowResult(number) =>
               ^
    Compilation Failed
    

    换句话说,我怀疑类型参数会改变评估事物的方式:

    • def runMe[B](dsl: DSL[B]): B 有一个类型参数,因此 match 内的每个 case 的结果将与 B 进行比较,其中 B 的每个案例值都可以证明是某种特定类型(Int、String)
    • def run: A 中,但是编译器以某种方式被阻止进行此类分析 - 恕我直言,这是一个错误,但可能是某些模糊功能的结果。

    从我看到的 Dotty 中发生了同样的错误,所以它要么是重复的错误,要么是类型级别检查器的限制(毕竟 GADT 还没有广泛使用 din Scala) - 我建议将问题报告给Scala/Dotty 团队并让他们决定它是什么。

    【讨论】:

      【解决方案2】:

      模式匹配似乎起作用differently,这取决于类型参数是来自封闭方法还是封闭类。这是一个使用类类型参数A的简化示例

      trait Base[T]
      case class Derived(v: Int) extends Base[Int]
      
      class Test[A] {
        def method(arg: Base[A]) = {
          arg match {
            case Derived(_) => 42
          }
        }
      }
      

      引发错误

      Error:(7, 12) constructor cannot be instantiated to expected type;
       found   : A$A87.this.Derived
       required: A$A87.this.Base[A]
            case Derived(_) => 42
                 ^
      

      虽然它使用方法类型参数A 起作用

      class Test {
        def method[A](arg: Base[A]) = {
          arg match {
            case Derived(_) => 42
          }
        }
      }
      

      SLS 8.4: Pattern Matching Expressions似乎解释了方法场景中发生的事情

      令?为选择器表达式?的类型,令?1,...,??为 包含模式匹配的所有方法的类型参数 表达。对于每个 ??,令 ?? 为其下界,?? 为 它的上限。每个模式?∈?1,,...,?? 可以输入两种 方法。首先,它尝试以 ? 作为其预期类型来键入 ?。 如果这失败了,? 改为使用修改后的预期类型 ?′ 这是由?通过替换类型的每次出现而产生的 未定义的参数??。

      AFAIU,我们有

      e = arg
      a1 = A
      T = Base[A]
      p1 = Derived(_) 
      

      首先,它尝试键入 ?,并将 ? 作为其预期类型,但是 Derived 不符合 Base[A]。因此它尝试了第二条规则

      如果失败,? 会改为使用修改后的预期类型 ?′ 这是由?通过替换类型的每次出现而产生的 未定义的参数??。

      假设 undefined 意味着类似存在类型,那么我们有T' = Base[_],因为以下确实成立

      implicitly[Derived <:< Base[_]]
      

      那么在方法类型参数的情况下模式匹配就变成了

      class Test {
        def method[A](arg: Base[A]) = {
          (arg: Base[_]) match {
            case Derived(_) => 42
          }
        }
      }
      

      确实可以编译。这似乎可以通过像这样成功编译类类型参数案例来确认

      class Test[A] {
        def method(arg: Base[A]) = {
          (arg: Base[_]) match {
            case Derived(_) => 42
          }
        }
      }
      

      因此,当类型参数来自封闭类时,type parameter inference for constructor patterns 似乎没有尝试第二条规则。


      至少这些似乎是一些动人的部分,希望有实际知识的人可以组合成连贯的解释,正如我主要猜测的那样。

      【讨论】:

        【解决方案3】:

        run 返回一个通用的A,但这仅适用于您没有评论的情况,因为您接受A 作为类型参数(编译器可以识别)。

        以下方法可以代替:

        sealed trait DSL[A] {
          def run(): A
        }
        
        case class GetLength(something: String) extends DSL[Int] {
          def run(): Int = something.length
        }
        case class ShowResult(number: Int) extends DSL[String] {
          def run(): String = s"the length is $number"
        }
        
        val dslGetLength = GetLength("123456789")
        val length = dslGetLength.run()
        val dslShowResult = ShowResult(length)
        println(dslShowResult.run())
        

        您可以使用此代码here on ScastieScala Fiddle

        【讨论】:

        • 如果有人在this 上进行模式匹配,我的第一直觉是说有人已经实现了一个vtable。 :)
        • 是的,事情可以像这样工作,就像在我的情况下 run[A] 之外的 DSL[A] 特征一样。但我就是想不通为什么这在特质中不起作用。我认为其他答案可能会提供线索,但无论如何谢谢。
        • 因为签名说您希望函数返回A,但实现在一种情况下返回Int,在另一种情况下返回String。您没有为编译器提供足够的信息来确定这些类型之间的关系。
        猜你喜欢
        • 2020-12-27
        • 2020-09-03
        • 2018-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-25
        相关资源
        最近更新 更多