C++23std::optional终于有了一些非常有用的补充。
由于我对 FP 的了解非常原始,我想知道以下两个操作的语法是什么(根据我的谷歌搜索是 2 个基本的单子操作):
- 一元绑定
- 一元回归
我最好的猜测是:
一元绑定是变换
monadic return 只是 C++17std::optional 构造函数(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_then(this绑定到第一个参数)。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
另一个挑剔的是,单子法则讨论的是表达式,而不是语句,因此您必须将构造函数包装在一个函数中。
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>并在仿函数内部从右T到U 右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是自定义点,想要选择某种类型的客户端代码必须为该类型编写自定义,并且也许可以利用成员函数。
回答这个问题又让我想到了另一个问题,所以我问了。