今天再来看看 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,通过一个函数,得到一个输出 bm 表示的就是 Monad,ab 前面都有 m,表示它们必须处于一定的上下文才能调用这个函数。此处它们满足的上下文就是 std::optional,基于此,函数才能知道可以怎么操作输入和输出参数。

如果输入和输出参数的类型都是 Monads,那么多个函数 f(), g(), h()… 就能够很容易地组合起来使用。

那如何让 std::optional 成为一个 Monad 呢?看看 C++23 新加的三个成员:

  • transform
  • and_then
  • or_else

具体意思不再赘述,transform 在 R1 中还叫 map,其实就和 Haskell 中的 map 相对应,and_then 对应 flatMapor_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 ,概念这里也没深入讲解,后面有空再来谈。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.