什么是 C++23 可选的 monadic bind 和 monadic return?
What are monadic bind and monadic return for C++23 optional?
C++23 std::optional
终于添加了一些非常有用的内容。
由于我对 FP 的了解非常原始,所以我想知道以下两个操作的语法是什么(根据我的谷歌搜索,这是两个基本的单子操作):
- 单子绑定
- 单子return
我最好的猜测是:
单子绑定是 transform
monadic return 只是 C++17 std::optional
constructor(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>;
区别在于被绑定或映射的函数的 return 类型。
这种区别的另一个例子是 .NET 的 linq Select
vs SelectMany
另一个挑剔之处在于 monad 法则讨论的是表达式,而不是语句,因此您必须将构造函数包装在一个函数中。
mbind
(不存在,我在模仿Haskell的>>=
)
在类似 C++ 的伪代码中,monadic 绑定,我们称之为 mbind
,应该具有这样的签名:
C<U> mbind(C<T>, std::function<C<U>(T)>);
即它应该在某种类型 T
上使用一个 monad C
,一个函数“拉出那个 monad 的 内部 " 并在(不一定)不同类型 U
、C<U>
上变成 monad 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>
并在函子 [=17] 内应用一个从 T
到 U
的函数=],从而得到 C<U>
.
所以还是有区别的
为了更好地理解区别是什么,请尝试传递 transform
一个 C<T>
和一个带有 mbind
期望的签名的函数,std::function<C<U>(T)>
.
你得到了什么?请记住,transform
应用函数“就在函子内部,没有拉出任何东西”,所以你得到一个 C<C<U>>
,即多一个函子层。
mbind
,相反,使用相同的两个参数,会给你一个 C<U>
.
以及如何从 transform(x, f)
returns 到 mbind(x, f)
returns,即从 C<C<U>>
到 C<U>
?您可以通过 Haskell 中的 join
和其他语言中的 flatten
中的 flatten/join/collapse/whatever-you-want-to-name-it 两个函数级别。
显然,如果你能做到这一点(你可以 C = std::optional
),那么那些“函数层”实际上就是“单子层”。
所以有没有类似mbind
的东西?
正如其他答案中所建议的那样,有 and_then
成员函数。
and_then
是我上面提到的(不存在的)mbind
吗?是的,它们之间的唯一区别 运行 与 transform
成员函数和 transform
自由函数之间运行的相同:一个是成员函数,另一个是自由函数。(¹)
flatten
/join
在哪里?
您可能想知道 C++23 中是否有此实用程序。我完全不知道,我几乎不知道 C++20 提供了什么。
但是,由于使 std::optional
成为仿函数的函数被定义为 std::optional
本身的成员函数,我坚信如果 std::optional
存在单子绑定函数,它也将被定义为一个成员函数,在这种情况下它将是 at this page,在 Monadic operations 部分,连同 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
因为前者是一个成员函数,后者是一个自由函数。
我强调它的原因是成员函数“属于”classes(从某种意义上说,没有 class 就不能拥有成员函数,它是的成员),所以在某种程度上,我们在这里讨论的 and_then
与我们为使 std::vector
成为 monad 而编写的同名函数完全无关,因为它会存在于 std::vector
中。
另一方面,非成员transform
和假设的mbind
是自由函数,所以它们的存在不需要任何class,所以它们看起来有点更像是一些类型可以选择加入的一般抽象的接口(比如 Haskell 的类型 classes)。很明显,假设 std::transform
和 mbind
是自定义点,想要选择某种类型的客户端代码必须为该类型编写自定义,也许这可以利用成员函数。
回答这个问题让我想到了另一个问题,所以I've asked it。
C++23 std::optional
终于添加了一些非常有用的内容。
由于我对 FP 的了解非常原始,所以我想知道以下两个操作的语法是什么(根据我的谷歌搜索,这是两个基本的单子操作):
- 单子绑定
- 单子return
我最好的猜测是:
单子绑定是 transform
monadic return 只是 C++17 std::optional
constructor(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>;
区别在于被绑定或映射的函数的 return 类型。
这种区别的另一个例子是 .NET 的 linq Select
vs SelectMany
另一个挑剔之处在于 monad 法则讨论的是表达式,而不是语句,因此您必须将构造函数包装在一个函数中。
mbind
(不存在,我在模仿Haskell的>>=
)
在类似 C++ 的伪代码中,monadic 绑定,我们称之为 mbind
,应该具有这样的签名:
C<U> mbind(C<T>, std::function<C<U>(T)>);
即它应该在某种类型 T
上使用一个 monad C
,一个函数“拉出那个 monad 的 内部 " 并在(不一定)不同类型 U
、C<U>
上变成 monad 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>
并在函子 [=17] 内应用一个从 T
到 U
的函数=],从而得到 C<U>
.
所以还是有区别的
为了更好地理解区别是什么,请尝试传递 transform
一个 C<T>
和一个带有 mbind
期望的签名的函数,std::function<C<U>(T)>
.
你得到了什么?请记住,transform
应用函数“就在函子内部,没有拉出任何东西”,所以你得到一个 C<C<U>>
,即多一个函子层。
mbind
,相反,使用相同的两个参数,会给你一个 C<U>
.
以及如何从 transform(x, f)
returns 到 mbind(x, f)
returns,即从 C<C<U>>
到 C<U>
?您可以通过 Haskell 中的 join
和其他语言中的 flatten
中的 flatten/join/collapse/whatever-you-want-to-name-it 两个函数级别。
显然,如果你能做到这一点(你可以 C = std::optional
),那么那些“函数层”实际上就是“单子层”。
所以有没有类似mbind
的东西?
正如其他答案中所建议的那样,有 and_then
成员函数。
and_then
是我上面提到的(不存在的)mbind
吗?是的,它们之间的唯一区别 运行 与 transform
成员函数和 transform
自由函数之间运行的相同:一个是成员函数,另一个是自由函数。(¹)
flatten
/join
在哪里?
您可能想知道 C++23 中是否有此实用程序。我完全不知道,我几乎不知道 C++20 提供了什么。
但是,由于使 std::optional
成为仿函数的函数被定义为 std::optional
本身的成员函数,我坚信如果 std::optional
存在单子绑定函数,它也将被定义为一个成员函数,在这种情况下它将是 at this page,在 Monadic operations 部分,连同 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
因为前者是一个成员函数,后者是一个自由函数。
我强调它的原因是成员函数“属于”classes(从某种意义上说,没有 class 就不能拥有成员函数,它是的成员),所以在某种程度上,我们在这里讨论的 and_then
与我们为使 std::vector
成为 monad 而编写的同名函数完全无关,因为它会存在于 std::vector
中。
另一方面,非成员transform
和假设的mbind
是自由函数,所以它们的存在不需要任何class,所以它们看起来有点更像是一些类型可以选择加入的一般抽象的接口(比如 Haskell 的类型 classes)。很明显,假设 std::transform
和 mbind
是自定义点,想要选择某种类型的客户端代码必须为该类型编写自定义,也许这可以利用成员函数。
回答这个问题让我想到了另一个问题,所以I've asked it。