Scala:什么都没有并抛出表达式
Scala: Nothing and throw expression
scala.Nothing 是一个没有该类型值的类型。
在 scala 中,'throw' 表达式的类型为 Nothing,并且表达式的计算结果也是一个值。
这两个不矛盾吗?
简答:
没有。这只是笨拙的措辞。在类型级别,表达式总是计算为某种类型。其中一种类型是 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")
}
以上函数returns一个String
。但是,第二种模式匹配情况会抛出异常。这会解析为 String
类型的值吗?不,它甚至没有首先解析为一个值!但是为了让这些东西在类型化系统的上下文中工作,我们必须为整个 throw ...
表达式分配一个类型。为了使这样的表达式在任何地方都有效,无论期望哪种类型(在上面的示例中是 String
),我们只需将其解析为底部类型 = Nothing
.
这意味着:
Throw 语句将在您放置它们的任何地方编译,这是首先具有此类语句的要求(否则您最好将它们从语言中删除)
关于 throw 语句被视为类型 Nothing
的表达式的推理是有道理的,因为 Nothing
是一种不能接受任何值的类型,而 throw
语句也永远无法解析为一个值。这是我之前关于它如何破坏程序结构的比喻——你输入了一个承诺解析为 String
的代码路径,但返回的不是 String
类型的值,你而是以某种方式逃脱而根本没有解析成一个值。请记住,纯 FP 语言从一开始就不应该允许异常存在。
Nothing
在 Scala 中有一些用法
在类型参数中表达差异(因为它从未被实例化,从类型系统的角度来看,这确实使底层实现更容易)
一段代码可能“return”类型 Nothing
如果它永远不会 return。这是 Unit
和 Nothing
之间的重要区别,在 Unit
中你仍然 return 但你 return 是一个空值,而在 [=10] 的情况下=] 你实际上 return。这实际上是异常处理的情况,这背后有充分的理由,传递程序的控制权。异常处理通常以类似于自动回调插入(ACI)/连续传递样式的方式实现using/in (CPS)。也就是说,当这段代码被编译时,异常抛出函数的代码中插入了回调,以通过一些自定义堆栈将控制权传递到需要处理异常的地方。借用其他语言,如果你对它的工作原理感兴趣,你可以看看 Scheme 的 call/cc
,或者 Racket 的 let/cc
,它几乎模拟了异常处理。
也就是做一些计算->当你遇到异常时,不管还有多少东西要计算,退出,丢弃所有东西,回到原来的堆栈。
scala.Nothing 是一个没有该类型值的类型。
在 scala 中,'throw' 表达式的类型为 Nothing,并且表达式的计算结果也是一个值。
这两个不矛盾吗?
简答:
没有。这只是笨拙的措辞。在类型级别,表达式总是计算为某种类型。其中一种类型是 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")
}
以上函数returns一个String
。但是,第二种模式匹配情况会抛出异常。这会解析为 String
类型的值吗?不,它甚至没有首先解析为一个值!但是为了让这些东西在类型化系统的上下文中工作,我们必须为整个 throw ...
表达式分配一个类型。为了使这样的表达式在任何地方都有效,无论期望哪种类型(在上面的示例中是 String
),我们只需将其解析为底部类型 = Nothing
.
这意味着:
Throw 语句将在您放置它们的任何地方编译,这是首先具有此类语句的要求(否则您最好将它们从语言中删除)
关于 throw 语句被视为类型
Nothing
的表达式的推理是有道理的,因为Nothing
是一种不能接受任何值的类型,而throw
语句也永远无法解析为一个值。这是我之前关于它如何破坏程序结构的比喻——你输入了一个承诺解析为String
的代码路径,但返回的不是String
类型的值,你而是以某种方式逃脱而根本没有解析成一个值。请记住,纯 FP 语言从一开始就不应该允许异常存在。
Nothing
在 Scala 中有一些用法
在类型参数中表达差异(因为它从未被实例化,从类型系统的角度来看,这确实使底层实现更容易)
一段代码可能“return”类型
Nothing
如果它永远不会 return。这是Unit
和Nothing
之间的重要区别,在Unit
中你仍然 return 但你 return 是一个空值,而在 [=10] 的情况下=] 你实际上 return。这实际上是异常处理的情况,这背后有充分的理由,传递程序的控制权。异常处理通常以类似于自动回调插入(ACI)/连续传递样式的方式实现using/in (CPS)。也就是说,当这段代码被编译时,异常抛出函数的代码中插入了回调,以通过一些自定义堆栈将控制权传递到需要处理异常的地方。借用其他语言,如果你对它的工作原理感兴趣,你可以看看 Scheme 的call/cc
,或者 Racket 的let/cc
,它几乎模拟了异常处理。
也就是做一些计算->当你遇到异常时,不管还有多少东西要计算,退出,丢弃所有东西,回到原来的堆栈。