当前位置:  开发笔记 > 编程语言 > 正文

如何在C++中完成currying?

如何解决《如何在C++中完成currying?》经验,为你挑选了7个好方法。

什么是currying?

如何在C++中完成currying?

请解释STL容器中的粘合剂?



1> Julian..:
1.什么是currying?

Currying只是意味着将几个参数的函数转换为单个参数的函数.使用示例可以很容易地说明这一点:

采用一个f接受三个参数的函数:

int
f(int a,std::string b,float c)
{
    // do something with a, b, and c
    return 0;
}

如果我们想要打电话f,我们必须提供它的所有论据f(1,"some string",19.7f).

那么一个curried版本f,让我们称curried_f=curry(f)它只需要一个参数,它对应于第一个参数f,即参数a.另外,f(1,"some string",19.7f)也可以使用curried版本编写curried_f(1)("some string")(19.7f).curried_f(1)另一方面,返回值只是另一个函数,它处理下一个参数f.最后,我们最终得到一个curried_f满足以下相等性的函数或可调用函数:

curried_f(first_arg)(second_arg)...(last_arg) == f(first_arg,second_arg,...,last_arg).
2.如何在C++中实现currying?

以下是有点复杂,但对我来说非常好(使用c ++ 11)...它也允许任意程度的卷曲,如此:auto curried=curry(f)(arg1)(arg2)(arg3)以及之后auto result=curried(arg4)(arg5).在这里:

#include 

namespace _dtl {

    template  struct
    _curry;

    // specialization for functions with a single argument
    template  struct
    _curry> {
        using
        type = std::function;

        const type
        result;

        _curry(type fun) : result(fun) {}

    };

    // recursive specialization for functions with more arguments
    template  struct
    _curry> {
        using
        remaining_type = typename _curry >::type;

        using
        type = std::function;

        const type
        result;

        _curry(std::function fun)
        : result (
            [=](const T& t) {
                return _curry>(
                    [=](const Ts&...ts){ 
                        return fun(t, ts...); 
                    }
                ).result;
            }
        ) {}
    };
}

template  auto
curry(const std::function fun)
-> typename _dtl::_curry>::type
{
    return _dtl::_curry>(fun).result;
}

template  auto
curry(R(* const fun)(Ts...))
-> typename _dtl::_curry>::type
{
    return _dtl::_curry>(fun).result;
}

#include 

void 
f(std::string a,std::string b,std::string c)
{
    std::cout << a << b << c;
}

int 
main() {
    curry(f)("Hello ")("functional ")("world!");
    return 0;
}

查看输出

好吧,正如Samer评论的那样,我应该补充一些关于它是如何工作的解释.实际的实现是在_dtl::_curry,而模板函数curry只是方便的包装器.该实现是对std::function模板参数的参数的递归FUNCTION.

对于只有一个参数的函数,结果与原始函数相同.

        _curry(std::function fun)
        : result (
            [=](const T& t) {
                return _curry>(
                    [=](const Ts&...ts){ 
                        return fun(t, ts...); 
                    }
                ).result;
            }
        ) {}

这里有一个棘手的问题:对于具有更多参数的函数,我们返回一个lambda,其参数绑定到调用的第一个参数fun.最后,剩下的N-1参数的剩余currying 被委托给_curry一个较少的模板参数的实现.

更新c ++ 14/17:

一个解决currying问题的新想法刚刚来到我身边......随着if constexprc ++ 17 的引入(并且在void_t确定一个函数是否完全通过的帮助下),事情似乎变得更加容易:

template< class, class = std::void_t<> > struct 
needs_unapply : std::true_type { };

template< class T > struct 
needs_unapply()())>> : std::false_type { };

template  auto
curry(F&& f) {
  /// Check if f() is a valid function call. If not we need 
  /// to curry at least one argument:
  if constexpr (needs_unapply::value) {
       return [=](auto&& x) {
            return curry(
                [=](auto&&...xs) -> decltype(f(x,xs...)) {
                    return f(x,xs...);
                }
            );
        };    
  }
  else {  
    /// If 'f()' is a valid call, just call it, we are done.
    return f();
  }
}

int 
main()
{
  auto f = [](auto a, auto b, auto c, auto d) {
    return a  * b * c * d;
  };

  return curry(f)(1)(2)(3)(4);
}

请参阅此处的代码.使用类似的方法,这里是如何使用任意数量的参数来curry函数.

如果我们constexpr if根据测试交换模板选择,那么同样的想法在C++ 14中似乎也有用needs_unapply::value:

template  auto
curry(F&& f);

template  struct
curry_on;

template <> struct
curry_on {
    template  static auto
    apply(F&& f) {
        return f();
    }
};

template <> struct
curry_on {
    template  static auto 
    apply(F&& f) {
        return [=](auto&& x) {
            return curry(
                [=](auto&&...xs) -> decltype(f(x,xs...)) {
                    return f(x,xs...);
                }
            );
        };
    }
};

template  auto
curry(F&& f) {
    return curry_on::value>::template apply(f);
}


+1这是第一个真正提供"如何在C++中完成currying?"的解决方案的答案.正如其他一些答案的评论中指出的那样,一般的currying(这里实现的)与部分应用有限数量的特定参数不同.
不要在C++代码中的任何位置使用双下划线(除非您是编译器供应商).这根本不合法.

2> Greg Hewgill..:

总之,钻营接受一个函数f(x, y)和给出一个固定的Y,赋予了新的功能g(x),其中

g(x) == f(x, Y)

可以在仅提供一个参数的情况下调用此新函数,并f使用fixed Y参数将调用传递给原始函数.

STL中的绑定器允许您为C++函数执行此操作.例如:

#include 
#include 
#include 

using namespace std;

// declare a binary function object
class adder: public binary_function {
public:
    int operator()(int x, int y) const
    {
        return x + y;
    }
};

int main()
{
    // initialise some sample data
    vector a, b;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);

    // here we declare a function object f and try it out
    adder f;
    cout << "f(2, 3) = " << f(2, 3) << endl;

    // transform() expects a function with one argument, so we use
    // bind2nd to make a new function based on f, that takes one
    // argument and adds 5 to it
    transform(a.begin(), a.end(), back_inserter(b), bind2nd(f, 5));

    // output b to see what we got
    cout << "b = [" << endl;
    for (vector::iterator i = b.begin(); i != b.end(); ++i) {
        cout << "  " << *i << endl;
    }
    cout << "]" << endl;

    return 0;
}


实际上,我认为这个答案是错误的; 这个答案描述的是"部分应用程序",`bind`函数可以做到."Currying"是将带有N个参数的函数转换为带有一个参数并返回带有N-1个参数的函数的函数的过程,例如`void(*)(int,short,bool)`变为`X(*)( int)```````void(*)(short,bool)`.请参阅http://www.haskell.org/haskellwiki/Currying,了解有关currying和部分功能应用的所有信息.
我希望我可以投票评论.这个评论链很有趣.:)
Marcin:在我添加它之前,raj请求了C++示例.:)
@Marcin:我来找到这个问题,因为我想_currying_而不仅仅是任何部分应用.不同之处在于,当您使用6个参数进行精心编写的函数以便您需要修复第一个参数时,您不希望将所有占位符放在那里!

3> Aaron..:

使用tr1简化Gregg的示例:

#include  
using namespace std;
using namespace std::tr1;
using namespace std::tr1::placeholders;

int f(int, int);
..
int main(){
    function g     = bind(f, _1, 5); // g(x) == f(x, 5)
    function h     = bind(f, 2, _1); // h(x) == f(2, x)
    function j = bind(g, _2);    // j(x,y) == g(y)
}

Tr1功能组件允许您在C++中编写丰富的功能样式代码.同样,C++ 0x也允许内联lambda函数执行此操作:

int f(int, int);
..
int main(){
    auto g = [](int x){ return f(x,5); };      // g(x) == f(x, 5)
    auto h = [](int x){ return f(2,x); };      // h(x) == f(2, x)
    auto j = [](int x, int y){ return g(y); }; // j(x,y) == g(y)
}

虽然C++没有提供一些面向功能的编程语言执行的丰富的副作用分析,但const分析和C++ 0x lambda语法可以帮助:

struct foo{
    int x;
    int operator()(int y) const {
        x = 42; // error!  const function can't modify members
    }
};
..
int main(){
    int x;
    auto f = [](int y){ x = 42; }; // error! lambdas don't capture by default.
}

希望有所帮助.


是的......这不是假的.

4> Konrad Rudol..:

看看Boost.Bind,它使Greg显示的过程更加通用:

transform(a.begin(), a.end(), back_inserter(b), bind(f, _1, 5));

此结合5f的第二个参数.

值得注意的是,这不是 currying(相反,它是部分应用程序).但是,在一般情况下使用currying在C++中很难(实际上,它最近才成为可能)并且经常使用部分应用程序.


我不能推荐Boost.Bind足够 - 学习它,使用它,喜欢它!

5> 小智..:

如果您使用的是C++ 14,那么非常简单:

template
auto curry(Function function, Arguments... args) {
    return [=](auto... rest) {
        return function(args..., rest...);
    }
}

然后你可以像这样使用它:

auto add = [](auto x, auto y) { return x + y; }

// curry 4 into add
auto add4 = curry(add, 4);

add4(6); // 10


从技术上讲,这是部分功能评估:完全咖喱将采用3参数函数`f`,并在调用之前允许`curry(f)(3)(2)(1)`.+1,因为部分功能评估仍然有用(通常人们的意思是"咖喱"),并且因为它有多紧.您可以使用更多代码和完美转发来提高性能.

6> missingfakto..:

其他答案很好地解释了粘合剂,所以我不会在这里重复这一部分.我将仅演示如何在C++ 0x中使用lambdas进行currying和部分应用程序.

代码示例:(注释中的说明)

#include 
#include 

using namespace std;

const function & simple_add = 
  [](int a, int b) -> int {
    return a + b;
  };

const function(int)> & curried_add = 
  [](int a) -> function {
    return [a](int b) -> int {
      return a + b;
    };
  };

int main() {
  // Demonstrating simple_add
  cout << simple_add(4, 5) << endl; // prints 9

  // Demonstrating curried_add
  cout << curried_add(4)(5) << endl; // prints 9

  // Create a partially applied function from curried_add
  const auto & add_4 = curried_add(4);
  cout << add_4(5) << endl; // prints 9
}



7> John Milliki..:

Currying是一种减少函数的方法,该函数将多个参数带入一系列嵌套函数,每个参数都有一个参数:

full = (lambda a, b, c: (a + b + c))
print full (1, 2, 3) # print 6

# Curried style
curried = (lambda a: (lambda b: (lambda c: (a + b + c))))
print curried (1)(2)(3) # print 6

Currying很好,因为您可以使用预定义的值定义简单包装其他函数的函数,然后传递简化函数.C++ STL绑定器在C++中提供了这种实现.

推荐阅读
惬听风吟jyy_802
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有