我知道这是一个老问题 但这里的答案并不完全正确 IMHO
Referential透明度 是指一个表达式如果其所属程序具有完全相同的含义,如果该表达式被其结果所取代,则该表达式具有完全相同的含义,那么该表达式的属性就是指该表达式的属性。应该清楚的是,提出一个例外违反了 特准透明度 ,因此有 副作用 。让我说明为什么...
使用 < em> Scala < / em > 来进行此示例。 考虑以下函数, 它需要一个整数参数 < code> > i code >, 并添加一个整数值 < code> j code>, 然后将结果以整数返回。 如果在添加两个值时出现例外, 它返回值 0。 阿拉斯, 计算 < code> j code > s 值会导致一个例外被丢弃( 为了简单起见, 我替换了 < code> j < / code> s 初始化表达式, 导致例外 )。
def someCalculation(i: Int): Int = {
val j: Int = throw new RuntimeException("Something went wrong...")
try {
i + j
}
catch {
case e: Exception => 0 // Return 0 if we catch any exception.
}
}
OK. 有点笨,但我正试图用一个非常简单的案例来证明一点。-)
s 在 scala REPL 中定义并调用此函数, 看看我们得到什么 :
$ scala
Welcome to Scala 2.13.0 (OpenJDK 64-Bit Server VM, Java 11.0.4).
Type in expressions for evaluation. Or try :help.
scala> :paste
// Entering paste mode (ctrl-D to finish)
def someCalculation(i: Int): Int = {
val j: Int = throw new RuntimeException("Something went wrong...")
try {
i + j
}
catch {
case e: Exception => 0 // Return 0 if we catch any exception.
}
}
// Exiting paste mode, now interpreting.
someCalculation: (i: Int)Int
scala> someCalculation(8)
java.lang.RuntimeException: Something went wrong...
at .someCalculation(<console>:2)
... 28 elided
很明显,有例外,没有意外
但请记住, 一种表达式是 < em> 优先透明 < / em >, 如果我们可以用其结果替换它, 使程序具有完全相同的含义。 在此情况下, 我们重新关注的表达式是 < code> j < / code > 。 让重构函数, 并替换 < code> j < / code > 及其结果( 需要声明被抛出例外的类型为整数, 因为这就是 < code > j < / code> s type ) :
def someCalculation(i: Int): Int = {
try {
i + ((throw new RuntimeException("Something went wrong...")): Int)
}
catch {
case e: Exception => 0 // Return 0 if we catch any exception.
}
}
现在让我们在REPL 中重新评价:
scala> :paste
// Entering paste mode (ctrl-D to finish)
def someCalculation(i: Int): Int = {
try {
i + ((throw new RuntimeException("Something went wrong...")): Int)
}
catch {
case e: Exception => 0 // Return 0 if we catch any exception.
}
}
// Exiting paste mode, now interpreting.
someCalculation: (i: Int)Int
scala> someCalculation(8)
res1: Int = 0
嗯,我想你可能已经预见到了: 那时我们的结果不同了。
如果我们计算j
,然后试图在 try
块中使用它,那么程序会抛出一个例外。然而,如果我们在块中以其值取代j
,我们就会得到一个 0。所以抛出例外显然违反了 优先透明度 。
我们应如何以 < em> 功能 < / em > 方式行事?? 通过不丢弃例外 。 在 < em> Scala em > (其他语言有等效) 中, 一种解决办法是在 < code> Try[ T] < / code > 类型中包装可能失败的结果: 如果成功, 结果将是 < code> 成功结果的 < success[ T] < / code > ; 如果失败, 结果将是包含相关例外的 < code> Failure[ < / code > ; 两个表达方式都是 < code> Try[ T] < / code > 的子类型。
import scala.util.{Failure, Try}
def someCalculation(i: Int): Try[Int] = {
val j: Try[Int] = Failure(new RuntimeException("Something went wrong..."))
// Honoring the initial function, if adding i and j results in an exception, the
// result is 0, wrapped in a Success. But if we get an error calculating j, then we
// pass the failure back.
j.map {validJ =>
try {
i + validJ
}
catch {
case e: Exception => 0 // Result of exception when adding i and a valid j.
}
}
}
注意:我们仍在使用 < / em> 例外,我们只是不使用 < em> throw < / them > 。
在 REPL 中尝试此选项:
scala> :paste
// Entering paste mode (ctrl-D to finish)
import scala.util.{Failure, Try}
def someCalculation(i: Int): Try[Int] = {
val j: Try[Int] = Failure(new RuntimeException("Something went wrong..."))
// Honoring the initial function, if adding i and j results in an exception, the
// result is 0, wrapped in a Success. But if we get an error calculating j, then we
// pass the failure back.
j.map {validJ =>
try {
i + validJ
}
catch {
case e: Exception => 0 // Result of exception when adding i and a valid j.
}
}
}
// Exiting paste mode, now interpreting.
import scala.util.{Failure, Try}
someCalculation: (i: Int)scala.util.Try[Int]
scala> someCalculation(8)
res2: scala.util.Try[Int] = Failure(java.lang.RuntimeException: Something went wrong...)
这次,如果我们用其价值取代j
,我们就会得到完全相同的结果,在所有情况中都是如此。
然而,还有另一种观点认为:如果在计算 j
值时出现例外的原因是由于我们编程不善(一个 错误 ),那么,提出例外(这将导致终止程序)可被视为提请我们注意这一问题的极好方法,然而,如果例外归根结底是超出我们直接控制范围的情况(例如增加的整数溢出的结果),而且我们应该能够从这样的条件中恢复过来,那么我们应该将这种可能性正式确定为函数返回值的一部分,并使用而不是放弃一种例外。