【c++】柯里化

refer: C++ 实现 Currying 和 Partial application

New Bing:

柯里化(Currying)是一种将多参数函数转换为一系列单参数函数的技术。它源自于数学家哈斯凯尔·柯里(Haskell Curry)的工作,因此得名。

柯里化的基本思想是将一个接受多个参数的函数转换为一系列接受单一参数的函数。例如,假设我们有一个接受两个参数的函数 f(x, y),我们可以将其柯里化为一个新函数 g(x)(y),其中 g(x) 返回一个新函数,该函数接受一个参数 y 并返回 f(x, y) 的结果。

柯里化的优点在于它能够让我们更灵活地使用函数。通过传递部分参数,我们可以创建新的函数,这些新函数固定了部分参数并等待剩余参数的传递。这样,我们就可以将复杂的函数拆分为多个简单的函数,并在不同的上下文中重用它们。

此外,柯里化还能够提高代码的可读性和可维护性。通过将复杂的函数拆分为多个简单的函数,我们可以更清楚地表达代码的意图,并更容易地理解和维护代码

使用c++ templatestd::function实现柯里化:

首先定义一个函数模板curry,模板参数为返回待处理函数类型RetT,变长形参类型...ArgsT

输入原函数构造的std::function,输出柯里化的std::function,显然,如果一个函数的形参数量少于2,则柯里化的函数即原函数,因此先使用if constexpr条件编译

template <typename RetT, typename... ArgsT>
auto curry(std::function<RetT(ArgsT...)> func) {
  if constexpr (sizeof...(ArgsT) < 2)
    return func;
  else
    return detail::curry_impl<RetT, ArgsT...>(func);
}

现在考虑真正需要柯里化的情况,如果记原函数为ff, 则输出应该为curry(f)curry(f)

curry(f)(x1)(x2)(x3)(xN)f(x1,x2,x3,...xN)curry(f)(x_1)(x_2)(x_3)\dots (x_N) \equiv f(x_1, x_2, x_3, ... x_N)

g(a)f(x1=a,x2,x3,,xN)g(a) \rightarrow f(x_1=a, x_2, x_3, \dots, x_N),则curry(g(a)())(x2)(x3)(xN)f(x1=a,x2,x3,...xN)curry(g(a)())(x_2)(x_3)\dots(x_N) \equiv f(x_1=a, x_2, x_3, ... x_N)

所以curry(g(a)())(x2)(x3)(xN)curry(f)(a)(x2)(x3)(xN)curry(g(a)())(x_2)(x_3)\dots(x_N) \equiv curry(f)(a)(x_2)(x_3)\dots (x_N)

因此curry(f)=acurry(g(a)())curry(f) = a\rightarrow curry(g(a)()),即curry(f)=acurry(f(x1=a,x2,x3,,xN))curry(f) = a \rightarrow curry(f(x_1=a, x_2, x_3, \dots, x_N))

因此可以使用递归闭包实现柯里化

  1. 递归边界
namespace {
namespace detail {
template <typename Return, typename Arg>
auto curry_impl(std::function<Return(Arg)> f) {
  return f;
}
} // namespace detail
} // namespace
  1. 递归闭包
namespace {
namespace detail { 
template <typename Return, typename Arg, typename... Args>
auto curry_impl(std::function<Return(Arg, Args...)> f) {
    // f(x_1, x_2, ... x_N)
  return [f](Arg&& arg) { // a -> curry(f(a, x_2, x_3, ..., x_N))
    return curry_impl( //curry(f(a, x_2, x_3, ..., x_N))
        std::function([&f, &arg](Args&&... args) {
      return f(std::forward<Arg>(arg), std::forward<Args>(args)...);
            // f(a, x_2, x_3, ..., x_N)
    }));
  };
}
} // namespace detail
} // namespace

complete code:

#include <iostream>
#include <functional>
#include <string>
namespace {
namespace detail {
template <typename Return, typename Arg>
auto curry_impl(std::function<Return(Arg)> f) {
  return f;
}

template <typename Return, typename Arg, typename... Args>
auto curry_impl(std::function<Return(Arg, Args...)> f) {
  return [f](Arg&& arg) {
    return curry_impl(std::function([f, &arg](Args&&... args) {
      return f(std::forward<Arg>(arg), std::forward<Args>(args)...);
    }));
  };
}
} // namespace detail
} // namespace
template <typename RetT, typename... ArgsT>
auto curry(std::function<RetT(ArgsT...)> func) {
  if constexpr (sizeof...(ArgsT) < 2)
    return func;
  else
    return detail::curry_impl<RetT, ArgsT...>(func);
}


使用例:

auto f(int a, int b, int c, int d, int e, int f) {
  return a + b + c + d + e + f;
}
auto f2(int a, int b) { return a + b; }
auto f3(std::string a, std::string b) { return a + b; }
int main() {
  auto cf1 = curry(std::function(f));
  std::cout << cf1(1)(1)(4)(5)(1)(4)<< std::endl;
  // 16
  auto cf2 = curry(std::function(f2));
  std::cout << cf2(2)(3) << std::endl;
  //5
  auto cf3 = curry(std::function(f3));
  std::cout << cf3(std::string{"hola"})(std::string{" amigo"}) << std::endl;
  // hola amigo
}