函数式编程

Written by - kok-s0s

C++

函数式编程 - 通常被视为面向对象的一种反机制。

什么是函数式编程

函数式编程的基础就是数学意义上的函数,是一种编程范式,是思考软件构建的一种方式。

然而,函数式编程范式的定义通常归因于它的积极属性。与其它编程范式,尤其是面向对象相比,这些属性被认为是有利的。具体如下:

  1. 通过避免(全局)共享可变的状态消除了副作用。在纯函数式编程中,函数调用没有任何副作用。

  2. 不可变的数据和对象。在纯函数式编程中,所有数据都是不可变的,即,数据结构一旦创建,它就永远不会被改变。相反,如果我们将函数应用于数据结构,那么将创建一个新的数据结构,它要么是新数据结构,要么是旧数据结构的变体。不可变数据具有线程安全的巨大优势。

  3. 函数组合和高阶函数。在函数式编程中,可以像对待数据一样对待函数。可以将函数存储在变量中,也可以将函数作为参数传递给其它函数,还可以将函数作为其它函数的返回结果。函数可以很容易地链接。换句话说,函数就是该语言的一等公民

  4. 更好更容易的并行化。并发性基本上很难保证。软件设计人员必须注意多线程环境中的许多事情,当只有一个线程时,通常不必担心,在多线程并发的程序中寻找错误可能会非常痛苦。但是如果函数的调用永远不会有任何副作用,如果没有全局状态,并且如果我们只处理不可变数据结构,那么使一个软件并行就容易得多。相反,使用命令式语言(如面向对象的语言)及其经常可变的状态时,需要锁和同步机制来保护数据不被多个线程同时访问和操作。

  5. 易于测试。如果纯函数具有上面提到的所有积极属性,那么它们也很容易被测试。在测试用例中没必要考虑全局可变状态或其它副作用。

什么是函数

函数式编程中的函数 == 真正的数学函数

其引用透明,即只要使用相同的输入调用函数,将始终得到相同的输出。

pure 函数和 impure 函数

对于相同的输入始终产生相同的输出(引用透明)的函数被称为纯函数

相比之下,命令式编程范式(如,过程化或面向对象编程)不能保证无副作用。

使用相同的参数调用可以产生不同输出的函数被称为impure 函数

现代 C++ 中的函数式编程

C++ 最开始的模板元编程(Template Metaprogramming, TMP)就有函数式编程的身影存在。

C++ 模板函数编程

模板元编程是函数式编程,且是图灵完备的。

在编译时能完成任何可能的计算。

缺点:如果使用大量的模板元编程,代码的可读性和可理解性会受到严重影响。

仿函数

在 C++ 中一只可以定义和使用所谓的像函数一样的对象,它们也被称为仿函数(Functor,另一个同义词是 Functional)。

仿函数基本上只是一个定义了括号运算符的类,即定义了 operator() 的类。实例化这些类之后,它们就可以像函数一样使用。

绑定和函数包装

std::bind

std::function

auto

用于 C++11 和 lambda 表达式的引入,这些模板已经很少被使用到了。

Lambda 表达式

闭包

在命令式编程中,习惯于当执行程序离开变量所在的作用域时,变量便不再可用。例如,如果函数已调用完成并返回结果给其调用者,该函数的所有局部变量将从调用到的堆栈中被删除,它们用到的内存也会被释放。

另一方面,在函数式编程中,可以构建一个闭包,它是一个具有持久的局部变量作用范围的函数对象。换句话说,闭包允许部分或全部局部变量的作用域与函数绑定,并且只要该函数存在,该作用域对象将一直存在。

在 C++ 中,在 lambda 表达式捕获列表的帮助下,可以创建此类闭包。闭包与 lambda 表达式不同,正如面向对象的对象(实例)与其类不同一样。

lambda 表达式的特殊之处在于它们通常是内联实现的,即在应用时实现的。这有时可以提高代码的可读性,编译器可以更有效地应用其优化策略。当然,lambda 函数也可以被视为数据,例如,储存在变量中,或作为函数参数传递给所谓的高阶函数。

lambda 表达式的基本结构如下所示:

[ capture list ]( parameter list ) -> return_type_declaration {
  lambda body
}

通用 Lambda 表达式(C++14)

从 C++14 开始,允许使用 auto 作为函数或 lambda 表达式的返回类型。即编译器被用于推断类型,这样 lambda 表达式被称为通用 lambda 表达式。

通用 lambda 表达式在与标准库算法的交互中非常有用,因为它们是普遍适用的。

高阶函数

函数式编程的核心概念就是所谓的高阶函数。它们是第一等函数的附属物。高阶函数是将一个或多个其它函数作为参数的函数,或者它们可以返回函数作为结果。

在 C++ 中,任何可调用对象,例如 std::function 包装的实例、函数指针、从 lambda 表达式创建的闭包、手动编写的仿函数以及其它任何实现了 operator() 的东西都可以作为参数传递给高阶函数。

C++17 推出 fold 表达式

整洁的函数式编程代码

无论使用何种编程风格,良好的软件设计原则仍然适用!

编写代码时,要总把维护你的代码的人当作是知道你住在哪里的暴力精神病患者。--- John F.Woods, 1991, in a post to the comp.lang.c++ newsgroup

Top