副作用(计算机科学)
在计算机科学中,如果操作,功能或表达对操作的调用者的价值。示例副作用包括修改非本地变量,修改静态局部变量,修改通过参考传递的可变参数,执行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