副作用 (计算机科学)

在计算机科学中,函数副作用(side effect)指当调用函数时,除了返回可能的函数值之外,还对主调用函数产生附加的影响。例如修改全局变量(函数外的变量),修改参数,向主调方的终端、管道输出字符或改变外部存储信息等。

在某些情况下函数副作用会给程序设计带来不必要的麻烦,给程序带来十分难以查找的错误,并降低程序的可读性与可移植性。严格的函数式语言要求函数必须无任何副作用,但功能性静态函数本身的目的正是产生某些副作用。在生命科学中,副作用往往带有贬义,但在计算机科学中,副作用有时正是“主要作用”。

下面是函数的副作用相关的几个概念,纯函数非纯函数参照透明性

纯函数

纯函数(英语:Pure Function)——输入输出数据流全是显式(英语:Explicit)的。

显式的意思是,函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。

非纯函数

如果一个函数通过隐式(英语:Implicit)方式,从外界获取数据,或者向外部输出数据,那么,该函数就不是纯函数,叫作非纯函数(英语:Impure Function)。

隐式的意思是,函数通过参数和返回值以外的渠道,和外界进行数据交换。比如,读取全局变量,修改全局变量,都叫作以隐式的方式和外界进行数据交换;比如,利用 I/O API(输入输出系统函数库)读取配置文件,或者输出到文件,打印到屏幕,都叫做隐式的方式和外界进行数据交换。

参照透明性

无副作用是参照透明性英语Referential transparency(英语:Referential Transparent)的必要非充分条件。参照透明意味着一个表达式(例如一次函数调用)可以被替换为它的值。这需要该表达式是纯的,也就是说该表达式必须是完全确定的(相同的输入总是导致相同的输出)而且没有副作用。

范例

f(x) { 
    return x + 1 
  }

f(x)函数就是纯函数

  a = 0 
  q(x) { 
    b = a 
  }

q(x)访问了函数外部的变量。q(x)是非纯函数

  p(x) { 
    print“hello” 
  }

p(x)通过I/O API输出了一个字符串。p(x)是非纯函数。

  c(x) { 
    // 假設readConfig()函數為I/O API的函數
    data = readConfig() // 读取配置文件 
  }

c(x)通过I/O API读取了配置文件。c(x)是非纯函数。

函数内部有隐式(Implicit)的数据流,这种情况叫做副作用(Side Effect)。上述的I/O,外部变量等,都可以归为副作用。因此,纯函数的定义也可以写为“没有副作用的函数”。

I/O API 可以看作是一种特殊的全局变量。文件、屏幕、数据库等输入输出结构可以看作是独立于运行环境之外的系统外全局变量,而不是应用程序自己定义的全局变量。

特殊的函数副作用

上述只讨论了一般的情况,还有一种特殊的情况,我们没有讨论。有些函数的参数是一种 In/Out 作用的参数,即函数可能改变参数里面的内容,把一些信息通过输入参数,夹带到外界。这种情况,严格来说,也是副作用。也是非纯函数。 比如下面的函数。

  process(context) {
    a = context.getInfo()
    result = calculate(a)
    context.setResult(result)
  }

纯函数的优点

纯函数的好处主要有几点:

  • 无状态,线程安全,不需要线程同步。
  • 纯函数相互调用组装起来的函数,还是纯函数。
  • 应用程序或者运行环境(Runtime)可以对纯函数的运算结果进行缓存,运算加快速度。