【问题标题】:Understanding Y Combinator through generic lambdas通过通用 lambda 了解 Y Combinator
【发布时间】:2016-06-07 03:29:51
【问题描述】:

在构建一个基于 lambda 的小型元编程库时,我不得不在 C++14 通用 lambda 中使用递归来实现 left-fold

我自己的解决方案是将 lambda 本身作为其参数之一传递,如下所示:

template <typename TAcc, typename TF, typename... Ts>
constexpr auto fold_l_impl(TAcc acc, TF f, Ts... xs)
{
    // Folding step.
    auto step([=](auto self)
    {
        return [=](auto y_acc, auto y_x, auto... y_xs)
        {
            // Compute next folding step.
            auto next(f(y_acc, y_x));

            // Recurse if required.
            return static_if(not_empty(y_xs...))
                .then([=]
                    {
                        // Recursive case.
                        return self(self)(next, y_xs...);
                    })
                .else_([=]
                    {
                        // Base case.
                        return next;
                    })();
        };
    });

    // Start the left-fold.
    return step(step)(acc, xs...);
}

step 是开始递归的“主要” lambda。它返回一个带有所需左折叠签名的函数(累加器、当前项、剩余项...)

函数使用self(self)(next, y_xs...)递归调用自身。

我最近遇到this proposal想在标准库中添加一个Y Combinator,看了之后觉得和我这里做的极其相似。

不幸的是,Y Combinator 的概念对我来说仍然没有“点击”——我遗漏了一些东西,我无法想象如何概括我对任何函数的 self 参数所做的事情,避免使用 step样板。

我已经阅读了this excellent StackOverflow answer 关于此事的信息,但它仍然没有为我“点击”。

(从那个答案)递归阶乘是这样定义的:

fact =
  (recurs) =>
    (x) =>
      x == 0 ? 1 : x * recurs(x - 1);

recurs 参数似乎与我的self 参数具有相同的作用。我不明白的是如何调用 recurs 而不将 recurs 再次传递给自身。

我必须像这样打电话给selfself(self)(params...)

但是,recurs 被称为 recurs(params...)

尝试调用self(params...) 会导致编译器错误,通知我self 只需要一个参数(这是auto self lambda 参数)

我在这里缺少什么?我如何重写我的fold_l_impl lambda,使其递归可以通过使用 Y Combinator 进行泛化?

【问题讨论】:

    标签: c++ recursion functional-programming c++14 y-combinator


    【解决方案1】:

    这是一个 y 组合,其中 lambda 传递了一个不需要传递的递归:

    template<class F>
    struct y_combinate_t {
      F f;
      template<class...Args>
      decltype(auto) operator()(Args&&...args)const {
        return f(*this, std::forward<Args>(args)...);
      }
    };
    template<class F>
    y_combinate_t<std::decay_t<F>> y_combinate( F&& f ) {
      return {std::forward<F>(f)};
    };
    

    然后你做:

      return y_combinate(step)(acc, xs...);
    

    改变

                       return self(self)(next, y_xs...);
    

                       return self(next, y_xs...);
    

    这里的技巧是我使用了一个非 lambda 函数对象,它可以访问它自己的 this,我将其作为第一个参数传递给 f

    【讨论】:

    • 谢谢,我现在明白了!当我意识到我想要做的是获取 lambda 的 *this 时,它“点击”了,这在目前是不可能的。
    • @VittorioRomeo 顺便说一句,self 应该被视为auto&amp;&amp; self 而不是auto self
    • 为什么是std::decay_t&lt;F&gt;?复制f重要吗?
    • @Barry 因为我要存储它并返回它?根据我的经验,存储和返回从通用代码中的函数参数推导出的引用是一个坏主意。想象一下auto fib(){ auto internal = [](auto&amp;&amp; self, int x){ if (x&lt;2) return 1; return self(x-1)+self(x-2); }; return y_combinate(internal); }——我们希望 lambda 与返回类型一样长,而不仅仅是internal
    • @Barry 是的。如果我们可以添加“返回值生命周期取决于参数的生命周期”,那将有所帮助;但即便如此,除了可能生成警告(返回值的生命周期取决于临时)。但是,如果调用者想要避免创建该副本,他们可以y_combinate(std::cref(f)),并且返回的函数对象将只保存一个(对)f 的(恒定引用),而不是一个副本,它会起作用。
    猜你喜欢
    • 2019-04-28
    • 2014-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多