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 Select
vsSelectMany
另一个挑剔的是,单子法则讨论的是表达式,而不是语句,因此您必须将构造函数包装在一个函数中。
mbind
(它不存在,我在模仿 Haskell 的>>=
)在类 C++ 的伪代码中,一元绑定,我们称之为它mbind
,应该有这样的语法:
C<U> mbind(C<T>, std::function<C<U>(T)>);
即它应该采用C
某种类型的 monad,一个“将那个 monad的内部T
拉出来”并变成(不一定)不同类型的 monad 的函数,并把它还给你。C
U
C<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
之间运行的相同:一个是成员,另一个是免费的。(¹)transform
transform
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::vector
monad 而编写的同名函数无关,因为它会存在于std::vector
.
另一方面,非成员函数transform
和假设mbind
函数是自由函数,因此它们不需要任何类就存在,因此它们看起来更像是类型可以选择加入的一些通用抽象的接口(如 Haskell 的类型类) . 很明显,假设std::transform
并且mbind
是自定义点,想要选择某种类型的客户端代码必须为该类型编写自定义,并且也许可以利用成员函数。
回答这个问题又让我想到了另一个问题,所以我问了。