Follow me on GitHub

基本概念 | 引用透明 & 纯函数 & 代换模型

函数式编程不鼓励使用副作用,并通过名字区分函数是否具有副作用:

  • 函数(function)一般就是指 纯函数,即无副作用(side effect)的函数
  • 过程(procedure)表示有副作用的具备参数的代码块

Haskell 从语法上区分具有副作用的函数,比 Scala 更加严格。

本文将简单介绍引用透明、纯函数以及代换模型的概念。

纯函数

纯函数必须满足两点要求:

  • 相同参数值,则函数结果总是相同
  • 无副作用

纯函数的计算结果只依赖参数值,不能依赖任何外部的可变状态,也不能产生任何副作用,常见的副作用有:

  • 修改变量
  • 抛出异常、错误
  • IO 输入、输出
  • 文件读写

使用自由变量(非参数,非局部变量)的函数 极有可能 不是纯函数,但 FP 语言一般通过闭包将自由变量的值 封闭 在函数中,因此大多数该类函数式纯函数。

下面是几个常见的非纯函数:

  • 返回当前时间的函数:因此每次都返回不同结果(它依赖一个全局变量)
  • random 函数
  • printf 函数

引用透明

我们可以用引用透明(referentially transparent)形式化地定义纯函数。

根据维基百科,引用透明是表达式的某种属性:

An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program’s behavior.

函数可以视为 带参数的表达式,因此若:

  • 输入参数表达式是引用透明的,且
  • 函数体表达式也是引用透明的

则该函数也是引用透明的,并称此类函数为纯函数。

对于纯函数而言,计算结果仅仅取决于输入参数,因此相同参数总是获得相同的计算结果。

代换模型

关于代换模型,在 计算机程序的构造与解释 有详细解释。

代换模型是一种简单、通用的求值模型:

  • 引用透明的表达式:可以将表达式本身 代换 为它的计算结果,而不改变程序的含义
  • 纯函数(引用透明):可以将函数调用 代换 为函数的返回值,同样不改变程序的含义

因此程序的求值过程转换为一系列的替换过程,这种求值模型被称为代换模型。

每次代换过,程序的一部分(表达式 or 函数调用)被其 等价值 替换,整个程序最后被归约为最简单的形式。

通过代换模型,可以对程序进行 推导(reason),这种推导能力本质是由 引用透明 赋予的。

代换模型有什么优势呢?

首先,并非只有代换模型可以推导,实际上所有求值模型都可以推导,但是代换模型 非常容易 推导。

因为推导时无需考虑类似全局状态之类的状态更新,这种推导是 局部 的,不必追踪函数调用前后的 状态变化,极大减轻了推导时的心智负担。

代换模型还有利于构建可复用的、模块化的组件,这类组件以纯函数的形式存在,由于纯函数分离了计算本身的逻辑,因此组件完全黑盒化,使用时完全不需要关心它们对上下文状态的影响(因为根本无影响),更容易组合、复用。


参考: