函数式c++使用auto映射组合器

Functional C++ map combinator using auto

本文关键字:映射 组合 auto 使用 c++ 函数      更新时间:2023-10-16

我尝试使用花哨的-std=c++14特性来实现函数式语言中的"map"组合符(不要与std::map混淆)。我的最终目标是为函数式编程编写一个"facade模式"头,使我能够在大多数时候忘记副作用和迭代器。我在https://gist.github.com/phatak-dev/766eccf8c72484ad623b上找到了一个志同道合的人的帖子。Madhukara版本的地图看起来像

template <typename Collection,typename unop>
  Collection map(Collection col,unop op) {
  std::transform(col.begin(),col.end(),col.begin(),op);
  return col;
}

只要您不要求一些愚蠢的东西,它似乎可以完美地工作,但返回类型必须与输入集合相同。我的尝试概括为拥有不同类型的域和范围如下:

template <typename Collection, typename function> 
auto map(function f, Collection c)  {
  auto result;
  std::transform(c.begin(),c.end(),result.begin(),f);
  return result;
}

这不能编译,但希望它清楚的人我想做什么…我想初始化一个f的输出类型为c的空的same-type-of-container-as,然后把f(c[i]) - s放进去。编译器抱怨说,"自动结果"的声明没有初始化,但我不知道如何要求一个空的无论什么。有没有办法调整这条线,让它做我想做的事?我以前从来没有尝试过用auto做这么奇特的事情,所以欢迎任何额外的建议。

谢谢!

约翰

编辑:这里有一个希望合理的例子:

auto first_letter = [](string s)  { return s[0]; }
vector<string> words; 
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map(first_letter, words); // {'h','w'}
编辑2:这是另一种使用重量级"流"库(不要与IO流混淆)来实现"迭代器模式"的方法,如Java的streams: http://jscheiny.github.io/Streams/

Java方法:http://tutorials.jenkov.com/java-collections/streams.html

这种流方法允许对容器类型有更多的选择自由(就像一些答案似乎支持的那样)和延迟求值。

直接使用boost:: adapters:: transformed:

#include <boost/range/adaptor/transformed.hpp>
template <typename Collection, typename function> 
auto map(function f, Collection c)  {
  return c | boost::adaptors::transformed(f);
}

使用这个范围-你可以创建任何你想要的容器。

char first_letter(string s)  { return s[0]; }
vector<string> words; 
words.push_back("hello"); words.push_back("world");
auto transformed_range = map(first_letter, words);
vector<char> first_letters(begin(transformed_range ), end(transformed_range ));

如果您坚持让map函数返回容器,而不是range -在此函数模板中添加一个参数:

#include <boost/range/adaptor/transformed.hpp>
template <typename Result, typename Collection, typename function> 
auto map(function f, Collection c)  {
  auto transformed_range = c | boost::adaptors::transformed(f);
  return Result(begin(transformed_range), end(transformed_range));
}
char first_letter(string s)  { return s[0]; }
vector<string> words; 
words.push_back("hello"); words.push_back("world");
vector<char> first_letters = map<vector<char>>(first_letter, words); 

但是如果你真的坚持要有你想要的确切的行为-你必须知道如何将集合类型转换为其他具有转换值的集合类型。

第一个-使用new_value_type:

的方法
template <typename Function, typename OldValueType>
struct MapToTransformedValue
{
    using type = decltype(std::declval<Function>()(std::declval<OldValueType>()));
};

总体特征:

template <typename Function, typename Container>
struct MapToTransformedContainer;

最简单的情况-对于std::array:

// for std::array
template <typename Function, typename OldValueType, std::size_t N> 
struct MapToTransformedContainer<Function, std::array<OldValueType, N>>
{
    using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
    using type =  std::array<value_type, N>;
};

对于std::vector—稍微复杂一点—您需要提供新的分配器,对于std分配器—您可以使用它的重新绑定模板:

// for std::vector
template <typename Function, typename OldValueType, typename OldAllocator> 
struct MapToTransformedContainer<Function, std::vector<OldValueType, OldAllocator>>
{
    using value_type = typename MapToTransformedValue<Function, OldValueType>::type;
    using allocator = typename OldAllocator::template rebind<value_type>::other;
    using type =  std::vector<value_type, allocator>;
};
所以你的函数看起来像这样:
template <typename Collection, typename function> 
auto map(function f, Collection c)  
{
    using NewCollectionType = typename MapToTransformedContainer<function, Collection>::type;
    auto transformed_range = c | boost::adaptors::transformed(f);
    return NewCollectionType (begin(transformed_range), end(transformed_range));
}

现在-你的main()是所需的:

char first_letter(std::string const& s)  { return s[0]; }
int main() {
    std::vector<std::string> words; 
    words.push_back("hello"); words.push_back("world");
    auto first_letters = map(first_letter, words); 
    std::cout << first_letters[0] << std::endl;
}

请注意,对于value_typeKey,Value对组成的其他容器,如std::map, std::set(以及它们的unordered_…你必须定义MapToTransformedContainer的另一个专门化

我将利用大多数容器都有接受一对迭代器的构造函数这一事实。

#include <boost/iterator/transform_iterator.hpp>
template <typename Function, typename Collection>
struct map_to
{
    Function f;
    const Collection& c;
    template <typename T>
    operator T() &&
    {
        using std::begin; using std::end;
        return { boost::make_transform_iterator(begin(c), f)
               , boost::make_transform_iterator(end(c), f) };
    }
};
template <typename Function, typename Collection> 
map_to<Function, Collection> map(Function f, const Collection& c)
{
    return { f, c };
}

测试:

int main()
{
    std::vector<std::string> words; 
    words.push_back("hello");
    words.push_back("world");
    auto first_letter = [](std::string s) { return s[0]; };
    std::vector<char> v = map(first_letter, words);
    std::set<char> s = map(first_letter, words);
    std::forward_list<char> f = map(first_letter, words);
}

只是为了好玩,下面是我对这个问题的尝试:

#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
template <template<typename...> class Collection> 
struct mapper {
    template<typename function, typename T, typename... Rest>
    static auto map(function f, const Collection<T, Rest...>& c)  {
      Collection<decltype(f(*c.begin()))> result;
      std::transform(c.begin(),c.end(),std::inserter(result, result.end()),f);
      return result;
    }
};
int main()
{
    // Example 1
    std::vector<int> v{0, 1};
    auto fv = mapper<std::vector>::map([](const auto& val) { return val + 0.1f; }, v);
    for (const auto& f : fv) { std::cout << f << " "; }
    std::cout << "n";
    // Example 2 
    std::set<float> m{1, 2, 3, 4}; 
    auto fm = mapper<std::set>::map([](const auto& val) { return static_cast<int>(val / 2.0f); }, m);
    for (const auto& f : fm) { std::cout << f << " "; }
}

请注意,只有当您对输出容器的类型参数以外的所有参数的默认值满意时,这才会起作用。

我也有同样的任务,如果这意味着减少样板文件,在功能上妥协是可以接受的。

这是我最终的结果(可能不适用于使用多个显式参数的容器,或者其模板参数无法简单推断,但您会同意它使用起来很干净)。

using std::vector;
template<typename Src, typename Dst, template<class, typename ...> typename Container>
Container<Dst> fmap(Container<Src>& container, Dst(*f)(Src)) {
    Container<Dst> result;
    result.reserve(container.size());
    std::transform(container.begin(), container.end(), std::back_inserter(result), f);
    return result;
}
int main() {
    vector<int> a = {1, 2, 3};
    auto f = [](int x) -> double { return (x + 1)/2; };
    vector<double> b = fmap(a, +f);
    for (auto x: b) {
        std::cout << x << std::endl;
    }
    return 0;
}

对我来说,一个主要的要求是不要让它使用起来很难看,而且它不应该使用boost。

编辑:如果你试图用一个非标准的分配器传递一个向量,这也会表现得很愚蠢(读:不编译)。