副作用(计算机科学)

计算机科学,如果操作功能表达对操作的调用者的价值。示例副作用包括修改非本地变量,修改静态局部变量,修改通过参考传递的可变参数,执行I/O或调用其他副作用的其他功能。在存在副作用的情况下,程序的行为可能取决于历史。也就是说,评估顺序很重要。了解和调试具有副作用的功能需要了解上下文及其可能的历史。

副作用在编程语言的设计和分析中起着重要作用。使用副作用的程度取决于编程范例。例如,命令式编程通常用于产生副作用,以更新系统的状态。相比之下,声明性编程通常用于报告系统状态,而没有副作用。

功能编程旨在最大程度地减少或消除副作用。缺乏副作用使对程序进行正式验证变得更加容易。功能性语言Haskell通过用Monadic动作代替I/O和其他状态计算等副作用。诸如标准ML方案Scala之类的功能性语言不限制副作用,但是程序员习惯避免使用它们。

汇编语言程序员必须意识到隐藏的副作用 - 修改处理器状态的一部分,这些副作用在指令的助记符中未提及。隐藏副作用的一个经典示例是算术指令,该指令隐式修改条件代码(隐藏的侧面效果),同时显式修改了寄存器(预期效果)。具有隐藏副作用的指令集的潜在缺点是,如果许多指令对单个状​​态(例如条件代码)具有副作用,那么顺序更新该状态所需的逻辑可能会成为性能瓶颈。对于某些使用管道式设计(自1990年以来)或订单外执行设计的处理器上,问题尤其严重。这样的处理器可能需要其他控制电路来检测隐藏的副作用,如果下一项指令取决于这些效果的结果,则管道停滞。

参照透明度

缺乏副作用是必要但不足以提高参照透明度的条件。参考透明度意味着可以用其值代替表达式(例如函数调用)。这要求表达式是纯粹的,也就是说,表达式必须是确定性的(始终给出相同输入的)和副作用不含。

时间副作用

在讨论副作用和参照透明度时,通常会忽略由执行操作所花费的时间引起的副作用。在某些情况下,例如使用硬件正时或测试,其中专门为其时间副作用插入操作,例如sleep(5000)或者for (int i = 0; i < 10000; ++i) {}。这些说明除了花费大量时间完成外,没有改变状态。

竭尽全力

如果子例程的多个应用程序对系统状态的影响与单个应用程序相同,那么带有副作用的子例程将是基本的,而换句话说,如果系统状态空间到本身与Subroutine相关的函数在数学感觉中是基本的。例如,考虑以下Python程序:

x = 0
def setx(n):
    global x
    x = n
setx(3)
assert x == 3
setx(3)
assert x == 3

setx是愿意的,因为第二次应用setx到3对系统状态的影响与第一个应用程序相同:x在第一个应用程序之后已经设置为3,并且在第二个应用程序之后仍将其设置为3。

如果在数学意义上是掌握的,则纯函数将是基本的。例如,考虑以下Python程序:

def abs(n):
    return -n if n < 0 else n
assert abs(abs(-3)) == abs(-3)

abs是愿意的,因为第二次应用abs第一个应用程序的返回值为-3,返回与第一个应用程序相同的值为-3。

例子

副作用行为的一种常见证明是c中的分配运算符。分配a = b是评估与表达式相同值的表达式b,具有存储R值的副作用b进入a。这允许多个分配:

a = (b = 3);  // b = 3 evaluates to 3, which then gets assigned to a

因为操作员的权利合伙人,这等同于

a = b = 3;

这为新手程序员带来了潜在的绞款,他们可能会混淆

while (b == 3) {}  // tests if b evaluates to 3

while (b = 3) {}  // b = 3 evaluates to 3, which then casts to true so the loop is infinite

也可以看看