【问题标题】:Scala: Nothing and throw expressionScala:Nothing 和 throw 表达式
【发布时间】:2020-12-12 09:45:32
【问题描述】:

scala.Nothing 是没有该类型值的类型。

在 scala 中,“throw”表达式的类型为 Nothing,并且表达式的计算结果也是一个值。

这两个不矛盾吗?

【问题讨论】:

  • 我认为区分throw expressions has type Nothingthrow returns Nothing很重要,你是说后者吗?
  • def error(message: String): Nothing = throw new RuntimeException(message) ....这段代码可能意味着 throw 表达式的类型为 Nothing。

标签: scala throw


【解决方案1】:

简答:

没有。这只是笨拙的措辞。在类型级别上,表达式总是计算为某种类型。其中一种类型是Nothing,即所谓的“底部类型”,它作为所有其他类型的子类位于底部。它是一种没有实际值的类型,这意味着解析为 Nothing 类型的表达式(例如 throw 表达式)不会计算为值。这是“表达式总是计算为值”规则的一个例外。如果 Scala 是一种纯 FP 语言,那么该规则将毫无例外地成立(完全是双关语)。

长答案:

抛出不仅在函数式编程中难以推理,因为它会产生副作用,而且在静态类型语言中也难以推理,因为如果没有值,就无法推理分配类型.而这正是这里发生的事情——而不是解析为一个值,你只是“爆炸”。抛出的异常超越了静态类型代码的界限,通过撕裂其结构并创建一个门户,允许它们在不同的维度继续他们的旅程。

抛开诗意的概念不谈,尽管在 Scala 等强静态类型的函数式编程语言的上下文中很难推理,但它需要存在(由于 Scala 基于 JVM 并且具有 OOP 方面作为好)所以它必须以某种方式与编译器和类型系统很好地配合。

所以我们所做的是,我们只需将此类表达式分配为Nothing 类型。请注意,我使用的是“表达式”这个词,因为通常我会说“分配这样的值”,但这里 没有值

看看下面的例子:

def myFunction(i: Int): String = i match {
  case 42 => "Fourty two"
  case _  => throw new Exception("Not fourty two")
}

上面的函数返回一个String。但是,第二种模式匹配情况会引发异常。这会解析为String 类型的值吗?不,它甚至一开始都没有解析为一个值!但是为了使这些事情在类型化系统的上下文中工作,我们必须为整个throw ... 表达式分配一个类型。为了让这样的表达式在任何地方都能工作,无论预期哪种类型(在上面的示例中是String),我们只需将其解析为底部类型=Nothing

这意味着:

  1. Throw 语句将在您放置它们的任何地方编译,这是首先拥有此类语句的要求(否则您不妨将它们从语言中删除)

  2. throw 语句被视为Nothing 类型的表达式的推理是有道理的,因为Nothing 是一种不能接受任何值的类型,而throw 语句也永远无法解析为值。这是我之前关于它如何破坏程序结构的隐喻——你输入一个承诺解析为String 的代码路径,但不是返回String 类型的值,而是以某种方式逃脱而没有永远解决成一个价值。请记住,纯 FP 语言从一开始就不应该允许存在异常。

【讨论】:

  • 感谢您的详细解答。这在 Haskell 等纯函数式语言中的处理方式是否不同?
  • 并非如此。 Haskell 仍然是图灵完备的语言,因此是部分语言。可以在 Haskell 中编写无限循环,但与其他所有表达式一样,无限循环必须计算为一个值,并且它必须有一个类型。 每个图灵完备的语言都有这个。每种图灵完备的语言都有永不完成的发散计算。那有什么价值呢?
  • 好点。也许我应该强调抛出异常和“有类型,但没有价值”概念之间的区别。前者在 Haskell 这样的语言中根本没有位置。
  • Haskell 实际上也有例外。许多人认为它们是设计错误。
  • 哦,好的。我对 Haskell 的体验仅限于业余项目,所以我从未接触过该语言的丑陋部分。 :)
【解决方案2】:

Nothing 在 Scala 中有几种用法

  1. 表达类型参数的变化(因为它从不被实例化,从类型系统的角度来看,这确实使底层实现更容易)

  2. 如果片段代码永远不会返回,它可能会“返回”Nothing 类型。这是UnitNothing 之间的重要区别,在Unit 中你仍然返回但你返回一个空值,而在Nothing 的情况下你实际上并没有返回。这实际上是异常处理的情况,这背后有充分的理由,传递程序的控制权。异常处理通常使用/以类似于自动回调插入(ACI)/继续传递样式(CPS)的方式实现)。也就是说,在编译此代码时,会在代码中插入回调,以便异常抛出函数通过一些自定义堆栈将控件传递到需要处理异常的地方。借用其他语言,如果您对它的工作原理感兴趣,可以查看 Scheme 的 call/cc 或 Racket 的 let/cc,它们几乎模拟了异常处理。

那就是做一些计算 -> 当你遇到一个异常时,不管有多少东西要计算,跳出,丢弃所有东西,回到原来的堆栈。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-21
    • 1970-01-01
    • 2019-10-30
    • 1970-01-01
    • 2020-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多