在C ++中实现和包装函数组合以进行惰性评估

时间:2019-04-12 17:02:47

标签: c++ functional-programming template-meta-programming

假设我有一个applicative的幼稚实现,这是我出于理智选择的名称,并不是我对其他语言的Applicative类型类一无所知。实现方法如下:

#include <iostream>
#include <string>

template <typename T>
struct applicative {
    template <typename Fn>
    auto then(Fn f) const {
        return applicative<decltype(f(data_))>{f(data_)};
    } 

    template <typename Fn>
    auto and_last(Fn f) const {
        return f(data_);
    }
    T data_;
};

int main() {
    applicative<std::string>{"hello world"}
    .then([](std::string const& s) {return s.size() * 4; })
    .then([](int k) {return k - 2; })
    .and_last([](int k) { std::cout << k << "\n"; });
} 

现在,可以在许多方面进行改进。提供make_applicative之类的东西用于就地构建,尝试消除多余的副本和移动(如果有的话)。但是从我看到我们用C ++编写函数的能力开始,我觉得有可能做得更好。任何撰写实现都可以使用,因此让我们在这个codereview.stackexchange问题中选择一个。有了它,我们可以说

auto f1 = [](std::pair<double,double> p) {return p.first + p.second; };
auto f2 = [](double x) {return std::make_pair(x, x + 1.0); };
auto f3 = [](double x, double y) {return x*y; };
auto g = compose(f1, f2, f3);

std::cout << g(2.0, 3.0) << std::endl;   //prints '13', evaluated as (2*3) + ((2*3)+1)

非常好。回到我的想法,我认为这应该可以通过以下方式对我的applicative实现进行重做:

auto sf = applicative<std::string>{}
    .then([](std::string const& s) {return s.size() * 4; })
    .then([](int k) {return k - 2; });

    std::cout << sf.eval_with("hello world"); << "\n"; 

如您所见,这是一种惰性评估,我们仅在需要时才使用eval_with来提供价值。我一直在考虑如何实现这个新版本一小时,而且我不知道在哪里存储操作和组合函数,applicative模板参数的内容,就像我们在std::string处所做的那样还有更多问题。一个人将如何实现这样的目标?是否像我最初希望的那样琐碎,还是需要大量代码?我真的很想要这样做,因为我认为这样可以避免在函数的长链上传递大量参数,从而使我受益匪浅。

编辑:我正在做更多工作,结果发现我链接的compose实现并不是我真正想到的,因为我们仍在执行链中的所有函数调用并仍在传递参数。但是您可以回答,假设任何compose函数都可以工作,并且选择更好的compose实现是性能优化。我的想法更像是遵循我的例子

applicative<std::string>{"hello world"}
    .then([](std::string const& s) {return s.size() * 4; })
    .then([](int k) {return k - 2; })
    .and_last([](int k) { std::cout << k << "\n"; });

这些then调用将导致与(s.size() * 4) - 2等效的单个函数调用,可以使用eval_with进行评估。

1 个答案:

答案 0 :(得分:2)

这就是你想要的吗?

#include <iostream>
#include <string>

struct id
{
    template <typename T>
    auto operator()(T t) const
    {
        return t;
    }
};

template <typename T, typename Func = id>
struct applicative {
    applicative(Func f = Func())
        : _f(f)
    {
    }

    template <typename Fn>
    auto then(Fn f) const {
        auto composition = [=](T val) { return f(_f(val)); };
        return applicative<T, decltype(composition)>(composition);
    } 

    auto eval_with(T t)
    {
        return _f(t);
    }

    Func _f;
};

int main() {
    auto sf = applicative<std::string>{}
    .then([](std::string const& s) {return s.size() * 4; })
    .then([](int k) {return k - 2; });

    std::cout << sf.eval_with("hello world") << "\n"; 
} 

免责声明:我并不担心完美的转发,因此一切都是通过价值传递的。