函数式编程有许多优势,由此越来越受欢迎。然而每个编程范式 (paradigm)
都有自己唯一的术语,函数式编程也不例外。我们提供一张术语表,希望使你学习函数式编程变得容易些。
本文档基于 functional-programing-jargon
进行改编,将原有的 JavaScript 示例替换为 C++ 实现,旨在帮助 C++
开发者理解函数式编程概念。
示例均为 C++。
尚在 WIP 阶段,欢迎交流。
如果适用,本篇文档使用定义在 Fantasy Land spec
中的术语。
Arity
函数参数的个数。来自于单词 unary (一元)、binary (二元)、ternary
(三元) 等等。这个单词是由 -ary 与 -ity
两个后缀拼接而成。例如,加法函数有两个参数,因此它被定义为二元函数
(binary function),或者说它的 arity 是
2。同样地,带有可变数量的参数的函数被称为 variadic。
1 2
| auto sum = [](int a, int b) { return a + b; };
|
高阶函数 (Higher-Order
Function / HOF)
以函数为参数或/和返回值的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <vector> #include <algorithm> #include <print>
template <typename Predicate, typename T> auto filter(Predicate predicate, const std::vector<T>& xs) { std::vector<T> result; std::copy_if(xs.begin(), xs.end(), std::back_inserter(result), predicate); return result; }
auto is_number = [](const auto& x) { return true; };
int main() { std::vector<int> nums = {0, 1, 2, 3}; auto filtered = filter([](int n) { return n % 2 == 0; }, nums); }
|
闭包 (Closure)
闭包是访问在其作用域外的变量的一种方式。在 C++ 中,Lambda
表达式通过捕获列表实现闭包。
1 2 3 4 5 6 7 8
| #include <print>
auto addTo = [](int x) { return [x](int y) { return x + y; }; };
auto addToFive = addTo(5); std::println("{}", addToFive(3));
|
在上面的例子中,x 被捕获到内部 Lambda 中,即使
addTo 执行完毕,内部 Lambda 依然可以访问
x。
偏函数应用 (Partial
Application)
「部分地」应用一个函数,即预设原始函数的部分参数来创建一个新的函数。在
C++ 中可以使用 std::bind 或者 Lambda 表达式。
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <functional> #include <print>
auto add3 = [](int a, int b, int c) { return a + b + c; };
auto fivePlus = [add3](int c) { return add3(2, 3, c); };
using namespace std::placeholders; auto fivePlusBind = std::bind(add3, 2, 3, _1);
std::println("{}", fivePlus(4));
|
柯里化 (Currying)
将一个多元函数转变为一元函数的过程。
每当函数被调用时,它仅仅接收一个参数并且返回带有一个参数的函数,直到传递完所有的参数。
1 2 3 4 5 6 7 8 9 10 11 12
| #include <print>
auto sum = [](int a) { return [a](int b) { return a + b; }; };
std::println("{}", sum(3)(4));
auto add2 = sum(2); std::println("{}", add2(10));
|
自动柯里化 (Auto Currying)
将一个包含多个参数的函数转换成另一个函数,这个函数如果被给到的参数少于正确的数量,就会返回一个接受剩余参数的函数。
C++ 标准库没有内置自动柯里化,但可以通过复杂的模板元编程实现。
函数组合 (Function
Composition)
把两个函数放在一起形成第三个函数的行为,一个函数的输入为另一个函数的输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <string> #include <print>
template <typename F, typename G> auto compose(F f, G g) { return [f, g](auto x) { return f(g(x)); }; }
auto floor_func = [](double x) { return static_cast<int>(x); }; auto to_string_func = [](int x) { return std::to_string(x); };
auto floorAndToString = compose(to_string_func, floor_func); std::println("{}", floorAndToString(12.12));
|
Continuation (后续)
在一个程序执行的任意时刻,尚未执行的代码称为 Continuation。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include <functional> #include <print> #include <string>
auto printAsString = [](int num) { std::println("Given {}", num); };
auto addOneAndContinue = [](int num, std::function<void(int)> cc) { int result = num + 1; cc(result); };
addOneAndContinue(2, printAsString);
|
纯函数 (Purity)
输出仅由输入决定,且不产生副作用。
1 2 3 4 5
| #include <string>
auto greet = [](const std::string& name) { return "hello, " + name; };
|
副作用 (Side effects)
如果一个函数或者表达式除了返回一个值之外,还与外部可变状态进行了交互(读取或写入),则它是有副作用的。
1 2 3 4
| int x = 0; auto sideEffect = [&x]() { x++; };
|
幂等 (Idempotent)
如果一个函数执行多次皆返回相同的结果,则它是幂等的。
1 2 3 4
| #include <cmath>
std::abs(std::abs(-10));
|
Point-Free 风格 (Point-Free
Style)
定义函数时,不显地指出函数所带参数。这种风格通常需要柯里化或者高阶函数。
1 2
| auto incrementAll = map(add(1));
|
断定 (Predicate)
根据输入返回 true 或 false。
1
| auto isGreaterTwo = [](int a) { return a > 2; };
|
契约 (Contracts)
契约规定了函数或表达式在运行时的行为的职责和保障。C++ 中可以使用
assert 或 C++ 20 的契约(虽然 C++ 20
契约被推迟,但概念一致)。
1 2 3 4 5 6
| #include <cassert>
auto addOne = [](int num) { assert(num > 0); return num + 1; };
|
范畴 (Category)
在范畴论中,范畴是指对象集合及它们之间的态射
(morphism)。在编程中,数据类型作为对象,函数作为态射。
遵从三个原则:
- 同一态射 (Identity)
- 可组合性 (Composition)
- 结合律 (Associativity)
值 (Value)
任何可以赋给变量的东西叫做值。
1 2 3 4 5
| #include <string>
5 std::string("hello") [](int a){ return a; }
|
常量 (Constant)
一旦被定义之后就不可以被重新赋值。
1 2
| const int five = 5; constexpr double pi = 3.14159;
|
函子 (Functor)
函子是一个实现了 map 的对象。在 C++
中,std::optional 或
std::vector(配合变换)可以看作函子。
1 2 3 4 5 6 7 8 9
| #include <optional>
template <typename T, typename F> auto map(const std::optional<T>& opt, F f) { if (opt) return std::optional(f(*opt)); return std::optional<decltype(f(*opt))>{std::nullopt}; }
auto result = map(std::optional(2), [](int x) { return x + 1; });
|
抬升 (Lift)
抬升是指将一个值放进一个对象(如函子)中。
1 2 3 4 5 6
| #include <optional>
template <typename T> std::optional<T> lift(T val) { return std::optional(val); }
|
引用透明性 (Referential
Transparency)
如果一个表达式能够被它的值替代而不改变程序的行为,则它是引用透明的。
等式推理 (Equational
Reasoning)
当一个应用程序由表达式组成并且没有副作用时,我们可以从这些组成部分中得知系统的真相。
Lambda
一种可以被视作一个值的匿名函数。
1
| auto add1 = [](int a) { return a + 1; };
|
惰性求值 (Lazy evaluation)
惰性求值是将表达式的求值延迟到需要它的值为止。C++ 20 的
std::ranges::views 提供了这种能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include <ranges> #include <print> #include <vector>
int main() { std::vector<int> nums = {1, 2, 3, 4, 5}; auto results = nums | std::views::transform([](int n) { std::println("Evaluating {}", n); return n * 2; }); for (int n : results | std::views::take(2)) { } }
|
幺半群 (Monoid)
一个对象,它拥有一个结合性的二元操作和单位元 (identity)。
1 2 3 4 5
|
1 + 0 == 1; (1 + 2) + 3 == 1 + (2 + 3);
|
单子 (Monad)
拥有 unit (或 of) 和 bind (或
chain) 的对象。C++ 23 为 std::optional 引入了
and_then。
1 2 3 4 5 6 7 8
| #include <optional> #include <string>
std::optional<int> get_id(std::string name) { } std::optional<std::string> get_name(int id) { }
auto result = get_id("Alice").and_then(get_name);
|
应用函子 (Applicative
Functor)
一个拥有 ap 函数的对象。ap
将对象中的函数应用于另一个同样类型的对象中的值。
态射 (Morphism)
一个变形函数。
- Endomorphism (自同态): 输入输出类型相同。
- Isomorphism (同构): 保持结构的双向变换。
Setoid
拥有 equals (在 C++ 中通常是 operator==)
的对象。
1 2 3 4 5 6 7 8
| #include <string>
struct Person { std::string name; bool operator==(const Person& other) const { return name == other.name; } };
|
半群 (Semigroup)
一个拥有结合性二元操作 (concat) 的对象。
1 2 3 4 5
| #include <string>
std::string a = "hello"; std::string b = " world"; auto c = a + b;
|
可折叠性 (Foldable)
一个拥有 reduce (C++ 中为 std::accumulate
或 std::ranges::fold_left) 函数的对象。
1 2 3 4 5
| #include <numeric> #include <vector>
std::vector<int> nums = {1, 2, 3}; int sum = std::accumulate(nums.begin(), nums.end(), 0);
|
类型签名 (Type Signatures)
在 C++ 中通常通过函数原型或 std::function 表示。
1 2 3 4
| #include <functional>
std::function<int(int, int)> add = [](int x, int y) { return x + y; };
|
代数数据类型 (Algebraic data
type)
- 和类型 (Sum type):
std::variant
- 积类型 (Product type):
std::tuple,
struct
1 2 3 4 5 6 7 8 9 10
| #include <variant> #include <string>
using Status = std::variant<int, std::string>;
struct Point { double x, y; };
|
可选类型 (Option)
在 C++ 中即 std::optional。
1 2 3 4 5 6
| #include <optional>
std::optional<int> safe_div(int a, int b) { if (b == 0) return std::nullopt; return a / b; }
|
偏函数 (Partial function)
偏函数是没有为全部参数定义的函数(例如除法在除数为 0 时未定义)。
处理偏函数的方法:
- 返回
std::optional
- 抛出异常
- 使用默认值
1 2 3 4 5 6 7
| #include <vector> #include <numeric>
int sum(const std::vector<int>& arr) { return std::accumulate(arr.begin(), arr.end(), 0); }
|
在 C++ 中的函数式编程库
参考资料