Enr*_*lis 8 c++ monads functional-programming functor c++23
std::transform来自<algorithm>标题的内容适用于范围,它“使”我们能够使用范围作为它们本身的函子(在范畴论的意义上(\xc2\xb9))。std::transform是基于迭代器的,是的,但std::ranges::views::transform不是,并且它的签名与函数语言中相应函数的签名密切匹配(对两个参数的不同顺序取模,但这没什么大不了的)。
当我看到这个问题(以及在回答它的过程中)时,我了解到 C++23 引入了std::optional<T>::transform,它std::optional也创建了一个函子。
所有这些消息确实让我兴奋,但我不禁想到函子是通用的,如果有一个统一的接口就好了transform任何函子都有一个统一的接口会很好,例如 Haskell 中的情况。
这让我认为类似的对象std::ranges::views::transform(具有不同的名称,而不是暗示ranges)可以成为一个定制点,STL不仅可以为范围定制,还可以为std::optionalSTL中的任何其他函子定制,而程序员可以为其用户定义的类定制它。
非常相似,C++23 也引入了std::optional<T>::and_then,它基本上是 的一元绑定std::optional。我不知道有任何类似的函数可以实现范围的单子绑定,但 C++20本质上是withsome_range | std::ranges::views::transform(f) | std::ranges::views::join的单子绑定。some_rangef
这让我认为可能存在一些通用接口,命名它mbind,人们可以选择使用任何类型。例如,STL 将选择std::optional通过按照 来实现它。std::optional<T>::and_then
该语言是否有机会或有计划有一天支持这种通用性?
\n我当然可以看到一些问题。今天 不完全是,破坏依赖于基于 SFINAE 的无效代码检测的代码是可以的。std::ranges::views::transform(some_optional, some_func)无效,因此某些代码可能依赖于 SFINAE。让它突然工作会破坏代码。
(\xc2\xb9) 关于函子这个词,我指的是范畴论中给出的定义(另见thisoperator() ),而不是“已定义的类的对象”的概念;后者没有在标准中的任何地方定义,甚至在cppreference中也没有提及,而是使用术语FunctionObject来引用
\n\n可以在函数调用运算符左侧使用的对象
\n
\n\n我不知道有任何类似的函数可以实现范围的单子绑定,但 C++20本质上是with
\nsome_range | std::ranges::views::transform(f) | std::ranges::views::join的单子绑定。some_rangef
ranges::views::for_each是范围的单子绑定(read),尽管它就views::transform | views::join在幕后。
至于您是否会获得 Functor 和 Monad 的通用接口。我对此表示怀疑,除非这种通用性对于库编写者编写模板有用。std::expiremental::future也是一元的(我想执行器也是),所以人们可以编写通用算法,例如foldM针对这三种类型的算法。我认为 Erik Niebler 已经用 range-v3 展示了可以编写一个 Functor/Monad 库,但代价是手动编码每个管道运算符,即
#include <fp_extremist.hpp>\ntemplate <typename M> requires Monad<M>\nauto func(M m)\n{\n return m\n | fp::extremist::fmap([](auto a) { return ...; })\n | fp::extremist::mbind([](auto b) { return ...; })\n ;\n}\nRun Code Online (Sandbox Code Playgroud)\n我认为实际上可能的是,我们将获得 UFCS 和一个|>运算符,这样我们就可以获得语法的好处invocable |> east和管道到算法的能力。来自巴里的博客:
\n\n它不起作用\xe2\x80\x99,因为虽然范围适配器是可传送的,但算法却不是。...
\n也就是说,x |> f 仍然像之前那样计算为 f(x)\xe2\x80\xa6,但 x |> f(y) 计算为 f(x, y)。
\n
PS 在 C++ 中给出 Functor 的定义并不难:<typename T> struct它提供了transform.
PSS\n编辑:我意识到如何处理应用程序。
\nApplicative<int> a, b, c;\nauto out = a\n | zip_transform(b, c\n , [](int a, int b, int c){ return a + b + c; })\n ;\nRun Code Online (Sandbox Code Playgroud)\nzip_transform因为它将a, b,压缩c为 aApplicative<std::tuple<int, int, int>>然后对其进行转换(想想optional, future, range)。虽然您始终可以使用部分应用函数的 Applicatives,但这会涉及大量嵌套函数,这不是 C++ 的风格,并且会破坏从上到下的阅读顺序。