发布于 2022-01-06 12:02:20
mbind (它不存在,我在模仿哈斯克尔的>>=)
在类似C++的伪代码中,一元绑定(让我们称之为mbind )应该具有这样的签名:
C<U> mbind(C<T>, std::function<C<U>(T)>);也就是说,它应该在某种类型的C上使用一个单模T,这个函数“将该单模的内部拉出来”,然后在一个(不一定)不同类型的U,C<U>上变成一个单一C,并将这个C<U>返回给您。
transform (自由函数)
首先,您提到的transform是一个成员函数,它有一个签名类型
C<U> C<T>::transform(std::function<U(T)>);但是,让我们重写它的签名,就像它是一个自由函数一样:
C<U> transform(C<T>, std::function<U(T)>);因此,正如您所看到的,它接受一个C<T>,并将一个函数从T应用到U ,就在函子中,从而产生一个C<U>。
所以有不同之处。
要更好地理解其中的区别,请尝试将transform传递给C<T>和一个带有签名的函数-- mbind所期望的函数std::function<C<U>(T)>。
你得到的是啥?请记住,transform应用的函数“就在函子内部,没有拔出任何东西”,因此您将得到一个C<C<U>>,即多一个函数层。
相反,使用相同的两个参数的mbind会给出一个C<U>。
您如何从transform(x, f)返回的内容转到mbind(x, f)返回的内容,即从C<C<U>>返回到C<U>?您可以通过Haskell中的flatten/join/collapse/whatever-you-want-to-name-it和其他语言中的flatten来实现这两个功能级别。
显然,如果您可以这样做(对于C = std::optional也可以),那么这些“功能层”实际上是“一元层”。
那么,有一个mbind-like的东西吗?
正如在另一个答案中所建议的,有and_then成员函数。
and_then是我前面提到的(不存在的) mbind吗?是的,它们之间唯一的区别是transform成员函数和transform空闲函数之间运行的相同:一个是成员,另一个是自由的。
顺便问一下,flatten/join在哪里?
您可能想知道C++23中是否有这个实用程序。我完全不知道,我几乎不知道C++20提供了什么。
但是,由于使std::optional成为函子的函数被定义为std::optional本身的成员函数,所以我强烈认为,如果std::optional有一元绑定函数,那么它也将被定义为成员函数,在这种情况下,它将是在这一页,在Monadic操作一节中,它与std::optional一起使用。因为没有,我倾向于假设它不存在。
但是,Haskell的一些知识可以帮助我了解为什么不需要将其添加到标准中。
如果你这么做会怎么样?
auto optOfOpt = std::make_optional(std::make_optional(3));
auto whatIsThis = optOfOpt.and_then(std::identity);是的,就是这样:在嵌套的可选项上调用.and_then(std::identity)等同于wannabe .join()。
其他图书馆呢?
Boost.Hana为Functor、Applicative、Monad和许多其他概念定义了概念,为您提供了一种自动实现所有抽象概念的方法,这些抽象概念利用这些概念的代价是给出一些最小的定义。例如,如果您在transform_impl和flatten_impl中为std::optional定义了namespace boost::hana,则允许您在其上使用transform、flatten、chain、ap和任何其他需要单点或更少的操作。
例如,下面是工作代码(编译器资源管理器的完整示例):
auto safeSqrt = [](auto const& x) {
return x > 0 ? std::optional(std::sqrt(x)) : std::nullopt;
};
{
auto opt = chain(std::optional(2), safeSqrt);
std::cout << opt.value_or(-1) << std::endl; // prints sqrt(2)
}
{
auto opt = chain(std::optional(-2), safeSqrt);
std::cout << opt.value_or(-1) << std::endl; // prints -1
}
{
auto opt = chain(std::nullopt, safeSqrt);
static_assert(std::is_same_v<decltype(opt), std::nullopt_t>); // passes
}(1)最初我更强调and_then不是mbind,因为前者是成员函数,后者是自由函数。
我之所以强调这一点,是因为成员函数“属于”类(也就是说,如果没有类的成员,就不可能有一个成员函数),所以在某种程度上,我们这里讨论的and_then与我们编写的名称函数完全无关,这个名称函数是为了使std::vector成为一个单体,因为它将驻留在std::vector中。
另一方面,非会员transform和假设的mbind是免费函数,因此它们不需要任何类,因此它们看起来更像一些类型可以选择的一般抽象的接口(比如Haskell的类型类)。显然,假设std::transform和mbind是定制点,希望选择某种类型的客户端代码必须为该类型编写自定义,这可能会利用成员函数。
对这个问题的回答让我想起了另一个问题,所以我已经问过了。
发布于 2022-01-06 11:40:48
不完全是。
在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绑定到第二个参数),它是函子操作(所有单子都是函子)。
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 vs SelectMany
另一个棘手的问题是,monad规则讨论的是表达式,而不是语句,因此必须将构造函数包装在函数中。
https://stackoverflow.com/questions/70606173
复制相似问题