T230807 Reviewing C++23 Monadic std::optional
今天再来看看 C++23 Monadic std::optional
,在 Overview of C++23 Features 只是简单介绍了用法,这里来说说设计原理。
std::optional
是一个 Monad,这个概念源于 FP (Functional Programming),对于 C++ coder 而言,理解起来并非易事。若是你熟悉 Haskell,这个就相当于里面的 Maybe
。Monads 叫作单子,是范畴论里面的概念,若要完全弄懂的话需要扩展许多概念,不符合 TGS 的定位,因此这里只是小做介绍。
简单来说,Monads 是 FP 当中的一个抽象数据类型,抽象的目的在于表示计算过程。通过这种方式,能够减少重复代码,简化组合流程。
用下面的公式来解释一下:
m a \rarr (a \to m b) \rarr m b
(a \rarr m b)
表示操作,a
是输入类型,b
是输出类型。操作必须满足某种上下文,才能知道怎样处理,m
就指定了这个上下文。如果将其替换为一个针对 std::optional
的函数,会更加容易理解:
std::optional<int> f(std::optional<std::string> a);
因此,其实说的就是有一个输入 a
,通过一个函数,得到一个输出 b
。m
表示的就是 Monad,a
和 b
前面都有 m
,表示它们必须处于一定的上下文才能调用这个函数。此处它们满足的上下文就是 std::optional
,基于此,函数才能知道可以怎么操作输入和输出参数。
如果输入和输出参数的类型都是 Monads,那么多个函数 f()
, g()
, h()
… 就能够很容易地组合起来使用。
那如何让 std::optional
成为一个 Monad 呢?看看 C++23 新加的三个成员:
transform
and_then
or_else
具体意思不再赘述,transform
在 R1 中还叫 map
,其实就和 Haskell 中的 map
相对应,and_then
对应 flatMap
,or_else
用来处理错误。
只有 transform
时,术语称 std::optional
为 Functor(函子),这并不是 C++ 中的仿函数。Monads 是在 Functor 的基础上扩展了一些操作,and_then
就是其中之一。
来对比一下 Monadic std::optional
前后的操作。
// Monadic std::optional
std::optional<std::string> ostr("42");
auto out = ostr
.and_then(std::stoi)
.transform([](auto i) { return i * 2; } );
// Non-monadic std::optional
std::optional<std::string> ostr("42");
std::optional<int> out{};
if (ostr.has_value()) {
out = std::stoi(ostr.value());
}
std::optional<int> final_out{};
if (out.has_value()) {
final_out = out.value() * 2;
}
可以看到,Monadic std::optional
消除了大量重复的检查代码,让我们直接专注于程序逻辑,而不必再被那些次要的细枝末节分散注意力。
标准中还有许多的 Monads ,概念这里也没深入讲解,后面有空再来谈。