NoS*_*tAl 14 c++ monads stdoptional c++23
C++23std::optional终于得到了一些非常有用的补充。
由于我对 FP 的了解非常原始,我想知道以下两个操作的语法是什么(根据我的谷歌搜索,这是两个基本的一元操作):
我最好的猜测是:
单子绑定就是变换
Monadic 返回只是 C++17std::optional 构造函数(8)
Enr*_*lis 15
mbind(它不存在,我模仿 Haskell 的>>=)在类似 C++ 的伪代码中,一元绑定(我们称之为mbind)应该具有这样的签名:
C<U> mbind(C<T>, std::function<C<U>(T)>);\nC即它应该采用某种类型的monad T,一个“将该monad 的内部C拉出”的函数,并变成(不一定)不同类型的monad U,C<U>并返回给您C<U>。
transform(免费功能)transform首先,你提到的是一个成员函数,它有一个这样的签名
C<U> C<T>::transform(std::function<U(T)>);\n但让我们重写它的签名,就像它是一个自由函数一样:
\nC<U> transform(C<T>, std::function<U(T)>);\n正如您所看到的,它需要 a并在函子内部从右到右C<T>应用一个函数,从而产生 a 。TU CC<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相同:一个是成员函数,另一个是自由函数。(\xe2\x80\xa0)transformtransform
flatten/在哪里?join您可能想知道 C++23 中是否有这个实用程序。我完全不知道,我几乎不知道 C++20 提供了什么。
\n然而,由于创建函子的函数std::optional被定义为它自己的成员函数std::optional,我坚信,如果存在单子绑定函数 for std::optional,它也会被定义为成员函数,在这种情况下,它会在这个页面,在Monadic 操作部分中,与and_then//一起。既然不存在,我倾向于假设它不存在。transformor_else
但是 Haskell 的一些知识可以帮助我了解为什么可能没有必要将其添加到标准中。
\n如果你这样做会发生什么?
\nauto optOfOpt = std::make_optional(std::make_optional(3));\nauto whatIsThis = optOfOpt.and_then(std::identity{});\n是的,就是这样:调用.and_then(std::identity{})嵌套可选相当于wannabe .join()。
Boost.HanaFunctor定义了、Applicative、和许多其他概念Monad,为您提供了一种自动实现利用这些概念的所有抽象的方法,但代价是给出一些最小的定义。例如,如果您在for中定义了transform_impland ,那么您就可以在 it 上使用、、、以及任何其他需要 monad 或更少的操作。flatten_implnamespace boost::hanastd::optionaltransformflattenchainap
例如,以下是工作代码(编译器资源管理器上的完整示例):
\n    auto safeSqrt = [](auto const& x) {\n        return x > 0 ? std::optional(std::sqrt(x)) : std::nullopt;\n    };\n    {\n        auto opt = chain(std::optional(2), safeSqrt);\n        std::cout << opt.value_or(-1) << std::endl; // prints sqrt(2)\n    }\n    {\n        auto opt = chain(std::optional(-2), safeSqrt);\n        std::cout << opt.value_or(-1) << std::endl; // prints -1\n    }\n    {\n        auto opt = chain(std::nullopt, safeSqrt);\n        static_assert(std::is_same_v<decltype(opt), std::nullopt_t>); // passes\n    }\n(\xe2\x80\xa0) 本来我更强调一点and_then不是,mbind因为前者是成员函数,后者是自由函数。
我之所以强调这一点,是因为成员函数“属于”类(从某种意义上说,如果没有它所属的类,就不能拥有成员函数),所以在某种程度上,我们在这里讨论的and_then是与我们编写的用于创建std::vectormonad 的同名函数完全无关,因为它将存在于std::vector.
另一方面,非成员函数transform和假设函数mbind都是自由函数,因此它们的存在不需要任何类,因此它们看起来更像是类型可以选择加入的一些通用抽象的接口(例如 Haskell\' s 类型类)。很明显,假设std::transform和mbind是定制点,想要选择某种类型的客户端代码必须为该类型编写定制,也许这可以利用成员函数。
回答这个问题让我的脑海中浮现出另一个问题,所以我问了这个问题。
\n不完全的。
在 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 的 linqSelect与SelectMany
另一个挑剔是 monad 法则讨论表达式,而不是语句,因此您必须将构造函数包装在函数中。
| 归档时间: | 
 | 
| 查看次数: | 2536 次 | 
| 最近记录: |