9

C++23std::optional终于有了一些非常有用的补充。

由于我对 FP 的了解非常原始,我想知道以下两个操作的语法是什么(根据我的谷歌搜索是 2 个基本的单子操作):

  1. 一元绑定
  2. 一元回归

我最好的猜测是:

一元绑定是变换

monadic return 只是 C++17std::optional 构造函数(8)

4

2 回答 2

8

不完全的。

在 Haskell 语法中,bind 的形式是m a -> (a -> m b) -> m b,对应于满足这个概念(对于所有A, B, F

template <class Fn, class R, class... Args>
concept invocable_r = std::is_invocable_r_v<R, Fn, Args...>;

template <class Bind, template <class> M, class A, class B, invokable_r<M<B>, A> F>
concept bind = invocable_r<Bind, M<B>, M<A>, F>;

那是and_thenthis绑定到第一个参数)。transform是 fmap(this绑定到第二个参数),它是 Functor 操作(所有 Monad 都是 Functor)。

fmap是形式(a -> b) -> f a -> f b

template <class Fmap, template <class> M, class A, class B, invokable_r<B, A> F>
concept fmap = invocable_r<Fmap, M<B>, M<A>, F>;

不同之处在于被绑定或映射的函数的返回类型。

这种区别的另一个例子是 .NET 的 linq SelectvsSelectMany

另一个挑剔的是,单子法则讨论的是表达式,而不是语句,因此您必须将构造函数包装在一个函数中。

于 2022-01-06T11:40:48.247 回答
7

mbind(它不存在,我在模仿 Haskell 的>>=

在类 C++ 的伪代码中,一元绑定,我们称之为它mbind,应该有这样的语法:

C<U> mbind(C<T>, std::function<C<U>(T)>);

即它应该采用C某种类型的 monad,一个“将那个 monad的内部T拉出来”并变成(不一定)不同类型的 monad 的函数,并把它还给你。CUC<U>C<U>

transform(免费的)

你提到的transform,首先,是一个成员函数,它有一个这样的签名

C<U> C<T>::transform(std::function<U(T)>);

但是让我们重写它的签名,就像它是一个自由函数一样:

C<U> transform(C<T>, std::function<U(T)>);

如您所见,它需要 aC<T>并在仿函数内部从右TU C应用一个函数,从而产生 a C<U>

所以有区别。

为了更好地理解区别是什么,请尝试传递transform一个C<T>和一个带有mbind预期签名的函数,std::function<C<U>(T)>.

你得到了什么?请记住,transform“就在函子内部,没有拉出任何东西”应用函数,所以你得到一个C<C<U>>,即多了一个函子层。

mbind,相反,使用相同的两个参数,会给你一个C<U>.

你怎么能从什么transform(x, f)返回到什么mbind(x, f)返回,即从 aC<C<U>>到 a C<U>join您可以通过在 Haskell 和flatten其他语言中调用的内容来展平/加入/折叠/无论您想要命名这两个功能级别。

显然,如果你能做到这一点(你也能做到C = std::optional),那么那些“功能层”实际上就是“单子层”。

那么有没有mbind类似的东西?

正如其他答案中所建议的那样,有and_then成员函数。

是我上面提到and_then的(不存在的)吗?是的,它们之间的唯一区别是在成员函数和自由函数mbind之间运行的相同:一个是成员,另一个是免费的。(¹)transformtransform

flatten顺便说一下/在哪里join

您可能想知道 C++23 中是否有此实用程序。我完全不知道,我几乎不知道 C++20 提供了什么。

但是,由于std::optional生成函子的函数被定义为std::optional自身的成员函数,我坚信如果有 monadic 绑定函数std::optional,它也会被定义为成员函数,在这种情况下,它会在这个页面,在Monadic 操作部分,与and_then/ transform/一起or_else。由于没有,我倾向于假设它不存在。

但是一些有关 Haskell 的知识可以帮助我了解为什么没有必要将它添加到标准中。

如果你这样做会发生什么?

auto optOfOpt = std::make_optional(std::make_optional(3));
auto whatIsThis = optOfOpt.and_then(std::identity);

是的,就是这样。


(¹) 本来我更强调and_then不存在,mbind因为前者是成员函数,后者是自由函数。

我强调它的原因是成员函数“属于”类(从某种意义上说,如果没有它所属的类,就不能拥有成员函数),所以在某种程度上and_then我们在这里讨论的完全是与我们为创建std::vectormonad 而编写的同名函数无关,因为它会存在于std::vector.

另一方面,非成员函数transform和假设mbind函数是自由函数,因此它们不需要任何类就存在,因此它们看起来更像是类型可以选择加入的一些通用抽象的接口(如 Haskell 的类型类) . 很明显,假设std::transform并且mbind是自定义点,想要选择某种类型的客户端代码必须为该类型编写自定义,并且也许可以利用成员函数。


回答这个问题又让我想到了另一个问题,所以我问了

于 2022-01-06T12:02:20.710 回答