【问题标题】:Implementing and wrapping function composition in C++ for lazy evaluation在 C++ 中实现和包装函数组合以进行惰性求值
【发布时间】:2019-09-03 12:11:05
【问题描述】:

假设我有一个 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++ 中编写函数的能力开始,我觉得更好的东西是可能的。任何 compose 实现都可以,所以让我们在这个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 进行评估。

【问题讨论】:

  • 你有什么想法?你总是不得不调用所有的函数,要么急切地(第一种情况),要么懒惰地(第二种情况)
  • 你的目标相当于一个咖喱compose
  • @Caleth 给出了std::bind,我认为如果涉及柯里化,它不会像我想要的那样高效?
  • 你可以先看看 boost::lambda 和 boost::yap。

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


【解决方案1】:

这是你想要的吗?

#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"; 
} 

免责声明:我不关心完美转发,所以一切都是按值传递的。

【讨论】:

  • 差不多,我有发明身份功能的想法,但无法实现。通过一些完美的转发,我可以将其缩减为一个额外的动作,将参数从eval_with 传递给第一个then 调用。谢谢。有没有机会你也知道这个东西是否对应于任何类别理论类型类?可能接近Functor
猜你喜欢
  • 2019-11-16
  • 1970-01-01
  • 2021-04-26
  • 2020-06-24
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-18
相关资源
最近更新 更多