English 中文(简体)
为什么提出例外是副作用?
原标题:Why is the raising of an exception a side effect?

根据的 Wikipedia 条目“http://en.wikipedia.org/wiki/Side_effect=28 computer_science%29" rel=“norefererr”>side effectation , 提出例外就是一种副作用。 想想这个简单的 Python 函数 :

def foo(arg):
    if not arg:
        raise ValueError( arg cannot be None )
    else:
        return 10

foo( noone) 引用它总是有例外。 相同的输入, 相同的输出。 它比较透明。 为什么这不是纯函数?

问题回答

只有遵守例外,并根据例外做出改变控制流程的决定,才能违反纯度。实际上,抛出例外值是相对透明的 -- -- 它在语义上等同于无终止或其他所谓的,http://www.haskell.org/haskellwiki/Bottom>底值。

如果一个(纯)函数不是 < a href="http://en.wikipedia.org/wiki/Partial_formation#Total_conference > 总计 ,那么它会评估到一个底值。您如何将底值编码到执行 - 可能是例外; 或未消灭, 或除以零, 或其他失败 。

考虑纯函数 :

 f :: Int -> Int
 f 0 = 1
 f 1 = 2

这并非对所有输入都定义。 对于某些输入, 它会从下到下评估。 执行过程会通过丢弃一个例外来编码它。 它应该与使用 < code> > 可能 或 < code> option 类型等同, 或使用 < code > option 类型 。

现在,当您“ eem> observation 底值并在此基础上作出决定时,你只能打破优惠透明度,因为这样可以引入非确定性,因为许多不同的例外可能会被抛出,而你却不知道。因此,哈斯凯尔的IO monad中就有例外,而产生所谓的 http://research.microsoft.com/en-us/um/people/simonpj/papers/imprecise-exn.htm> "imprecise < a > 例外可以纯粹地完成。

因此,提出例外本身就是一种副作用是不真实的。问题在于,你是否可以改变一个纯粹的功能的行为,其基础是非同寻常的价值 -- -- 从而打破优惠透明度 -- -- 这就是问题所在。

从第一行:

"In computer science, a function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world"

它修改的状态是程序的终止。 要回答您关于为什么它不是纯函数的另一个问题。 函数不是纯函数, 因为扔一个例外就终止了程序, 因此它具有副作用( 您的程序结束 ) 。

我知道这是一个老问题 但这里的答案并不完全正确 IMHO

Referential透明度 是指一个表达式如果其所属程序具有完全相同的含义,如果该表达式被其结果所取代,则该表达式具有完全相同的含义,那么该表达式的属性就是指该表达式的属性。应该清楚的是,提出一个例外违反了 特准透明度 ,因此有 副作用 。让我说明为什么...

使用 < em> Scala < / em > 来进行此示例。 考虑以下函数, 它需要一个整数参数 < code> > i , 并添加一个整数值 < code> j , 然后将结果以整数返回。 如果在添加两个值时出现例外, 它返回值 0。 阿拉斯, 计算 < code> j 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 (其他语言有等效) 中, 一种解决办法是在 < 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 值时出现例外的原因是由于我们编程不善(一个 错误 ),那么,提出例外(这将导致终止程序)可被视为提请我们注意这一问题的极好方法,然而,如果例外归根结底是超出我们直接控制范围的情况(例如增加的整数溢出的结果),而且我们应该能够从这样的条件中恢复过来,那么我们应该将这种可能性正式确定为函数返回值的一部分,并使用而不是放弃一种例外。

公文透明度还有可能以计算本身的结果取代计算(如函数援引),而如果函数引起例外,则无法做到这一点。这是因为例外不参与计算,但必须赶上!

提出例外可以是纯粹的或非纯粹的,而只是取决于提出的例外类型。 良好的幽默规则是,如果例外由代码提出,它是纯洁的,但如果由硬件提出,则通常必须归类为非纯粹的。

可以通过查看硬件提出例外时发生的情况来看到这一点:首先,发出中断信号,然后中断处理器开始执行。这里的问题是,中断处理器不是对您功能的争论,也不是在您函数中指定的,而是全球变量。每当阅读或写入一个全球变量(aka state)时,你就不再有纯粹的功能。

相比之下, 在您的代码中提出例外 : 您从一组已知的、 本地范围参数或常数构建例外值, 并“ 扔掉” 结果 。 没有使用全球变量 。 丢弃例外的过程基本上是由您语言提供的合成糖, 它不会引入任何非确定性或非纯性的行为 。 正如 Don 所说 : “ 它应该与 使用一种可能或选项类型具有等同的音义性 ”, 这意味着它也应该具有所有相同的属性, 包括纯度 。

当我说提出硬件例外被“通常”归类为副作用时,情况并不一定总是这样。例如,如果您的代码运行中的计算机在其引起例外时不要求中断,而是将特殊值推到堆叠上,那么它就不能归类为非纯值。 我认为IEEE浮点NAN错误使用特殊值而不是中断,所以在做浮动点数学时提出的任何例外都可以被归类为无副作用,因为该值不是从任何全球状态读取的,而是一直被编码到 FPU 中。

寻找所有要求, 使一块代码是纯净的, 以代码为基础的例外, 并抛出语句甘蔗组合键 在所有框中, 它们不修改任何状态, 它们与调用功能没有任何互动, 或任何超出其引用范围的东西, 并且它们比较透明, 但只有在编译者对您的代码有办法时, 才能使用它 。

与所有纯讨论和非纯讨论一样,我排除了任何执行时间或记忆操作的概念,并假定可以执行的任何职能,不管其实际执行与否,都完全可以执行。 我也没有关于IEEE浮点NAN例外索赔的证据。





相关问题
external side effect in constructor

Look at this code: #include <framework_i_hate.h> int main() { XFile file("./my_file.xxxx", "create"); XObject object("my_object"); // modify the object object.Write(); } Try to guess ...

Unit testing functions with side effects?

Let s say you re writing a function to check if a page was reached by the appropriate URL. The page has a "canonical" stub - for example, while a page could be reached at stackoverflow.com/questions/...

Why the output for "a" is -80?

#include<stdio.h> #include<conio.h> #define ABC 20 #define XYZ 10 #define XXX ABC - XYZ void main() { int a; a = XXX * 10; printf(" %d ", a); getch(); } I ...

Fine-grained decorator pattern

I understand the Decorator pattern, in it s simplest terms. The idea being that one class wraps another, where a decorator method wishes to run some other code before and/or after calling the same ...

Are side-effects possible in pure functional programming

I have been trying to wrap my head around functional programming for a while now. I have looked up lambda calculus, LISP, OCaml, F# and even combinatorial logic but the main problem I have is this - ...

热门标签