カリー化とは?
C++でカリー化を行うにはどうすればよいですか?
STLコンテナのバインダーについて説明してください。
つまり、カリー化は関数f(x, y)
を取り、固定のY
が与えられると、新しい関数g(x)
を与えます。
g(x) == f(x, Y)
この新しい関数は、引数が1つだけ指定されている状況で呼び出され、固定のf
引数を使用して元のY
関数に渡されます。
STLのバインダーを使用すると、C++関数に対してこれを行うことができます。例えば:
#include <functional>
#include <iostream>
#include <vector>
using namespace std;
// declare a binary function object
class adder: public binary_function<int, int, int> {
public:
int operator()(int x, int y) const
{
return x + y;
}
};
int main()
{
// initialise some sample data
vector<int> 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<int>::iterator i = b.begin(); i != b.end(); ++i) {
cout << " " << *i << endl;
}
cout << "]" << endl;
return 0;
}
カリー化とは、単に複数の引数の関数を単一の引数の関数に変換することを意味します。これは、例を使用して最も簡単に説明できます。
3つの引数を受け入れる関数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)
を指定する必要があります。
次に、カレーバージョンのf
を、curried_f=curry(f)
と呼びましょう。これは、f
の最初の引数、つまり引数a
に対応する単一の引数のみを想定しています。また、f(1,"some string",19.7f)
はカレーバージョンを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).
以下はもう少し複雑ですが、私にとっては非常にうまく機能します(c ++ 11を使用)...次のように任意の程度のカリー化も可能です:auto curried=curry(f)(arg1)(arg2)(arg3)
以降auto result=curried(arg4)(arg5)
。ここに行きます:
#include <functional>
namespace _dtl {
template <typename FUNCTION> struct
_curry;
// specialization for functions with a single argument
template <typename R,typename T> struct
_curry<std::function<R(T)>> {
using
type = std::function<R(T)>;
const type
result;
_curry(type fun) : result(fun) {}
};
// recursive specialization for functions with more arguments
template <typename R,typename T,typename...Ts> struct
_curry<std::function<R(T,Ts...)>> {
using
remaining_type = typename _curry<std::function<R(Ts...)> >::type;
using
type = std::function<remaining_type(T)>;
const type
result;
_curry(std::function<R(T,Ts...)> fun)
: result (
[=](const T& t) {
return _curry<std::function<R(Ts...)>>(
[=](const Ts&...ts){
return fun(t, ts...);
}
).result;
}
) {}
};
}
template <typename R,typename...Ts> auto
curry(const std::function<R(Ts...)> fun)
-> typename _dtl::_curry<std::function<R(Ts...)>>::type
{
return _dtl::_curry<std::function<R(Ts...)>>(fun).result;
}
template <typename R,typename...Ts> auto
curry(R(* const fun)(Ts...))
-> typename _dtl::_curry<std::function<R(Ts...)>>::type
{
return _dtl::_curry<std::function<R(Ts...)>>(fun).result;
}
#include <iostream>
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;
}
OK、Samerがコメントしたように、これがどのように機能するかについていくつかの説明を追加する必要があります。実際の実装は_dtl::_curry
で行われますが、テンプレート関数curry
は便利なラッパーにすぎません。実装は、std::function
テンプレート引数FUNCTION
の引数に対して再帰的です。
引数が1つしかない関数の場合、結果は元の関数と同じになります。
_curry(std::function<R(T,Ts...)> fun)
: result (
[=](const T& t) {
return _curry<std::function<R(Ts...)>>(
[=](const Ts&...ts){
return fun(t, ts...);
}
).result;
}
) {}
ここで注意が必要なのは、引数が多い関数の場合、引数がfun
の呼び出しの最初の引数にバインドされているラムダを返すことです。最後に、残りのN-1
引数の残りのカリー化は、テンプレート引数が1つ少ない_curry<Ts...>
の実装に委任されます。
カレー化の問題に取り組む新しいアイデアが思い浮かびました... c ++ 17にif constexpr
を導入しました(そして関数が完全にカレー化されているかどうかを判断するためにvoid_t
の助けを借りて) 、物事ははるかに簡単になるようです:
template< class, class = std::void_t<> > struct
needs_unapply : std::true_type { };
template< class T > struct
needs_unapply<T, std::void_t<decltype(std::declval<T>()())>> : std::false_type { };
template <typename F> 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<decltype(f)>::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);
}
ここ で動作中のコードを参照してください。同様のアプローチで、 here は、任意の数の引数を持つ関数をカレーする方法です。
テストneeds_unapply<decltype(f)>::value
に応じてconstexpr if
をテンプレート選択と交換すると、C++ 14でも同じ考えがうまくいくようです。
template <typename F> auto
curry(F&& f);
template <bool> struct
curry_on;
template <> struct
curry_on<false> {
template <typename F> static auto
apply(F&& f) {
return f();
}
};
template <> struct
curry_on<true> {
template <typename F> static auto
apply(F&& f) {
return [=](auto&& x) {
return curry(
[=](auto&&...xs) -> decltype(f(x,xs...)) {
return f(x,xs...);
}
);
};
}
};
template <typename F> auto
curry(F&& f) {
return curry_on<needs_unapply<decltype(f)>::value>::template apply(f);
}
Tr1を使用して、Greggの例を単純化します。
#include <functional>
using namespace std;
using namespace std::tr1;
using namespace std::tr1::placeholders;
int f(int, int);
..
int main(){
function<int(int)> g = bind(f, _1, 5); // g(x) == f(x, 5)
function<int(int)> h = bind(f, 2, _1); // h(x) == f(2, x)
function<int(int,int)> j = bind(g, _2); // j(x,y) == g(y)
}
Tr1関数型コンポーネントを使用すると、C++で豊富な関数型コードを記述できます。同様に、C++ 0xでは、インラインラムダ関数でもこれを実行できます。
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ラムダ構文が役立ちます。
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.
}
お役に立てば幸いです。
Boost.Bind を見てください。これにより、Gregが示すプロセスの用途が広がります。
transform(a.begin(), a.end(), back_inserter(b), bind(f, _1, 5));
これにより、5
がf
の2番目の引数にバインドされます。
これはnotカリー化ではないことに注意してください(代わりに、部分適用です)。ただし、C++では一般的な方法でカリー化を使用するのは難しく(実際、最近になってようやく可能になりました)、代わりに部分適用がよく使用されます。
C++ 14を使用している場合、それは非常に簡単です。
template<typename Function, typename... Arguments>
auto curry(Function function, Arguments... args) {
return [=](auto... rest) {
return function(args..., rest...);
}; // don't forget semicolumn
}
その後、次のように使用できます。
auto add = [](auto x, auto y) { return x + y; }
// curry 4 into add
auto add4 = curry(add, 4);
add4(6); // 10
他の回答はバインダーをうまく説明しているので、ここではその部分を繰り返しません。 C++ 0xのラムダを使用してカリー化と部分適用を実行する方法のみを示します。
コード例:(コメントでの説明)
#include <iostream>
#include <functional>
using namespace std;
const function<int(int, int)> & simple_add =
[](int a, int b) -> int {
return a + b;
};
const function<function<int(int)>(int)> & curried_add =
[](int a) -> function<int(int)> {
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
}
カリー化は、複数の引数を、それぞれ1つの引数を持つ入れ子関数のシーケンスに取り込む関数を減らす方法です。
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
事前定義された値を使用して他の関数の単なるラッパーである関数を定義してから、簡略化された関数を渡すことができるため、カリー化は便利です。 C++ STLバインダーは、C++でこれを実装します。
ここにいくつかの素晴らしい答えがあります。コンセプトをいじってみるのも楽しかったので、自分で追加しようと思いました。
部分関数の適用:一部のパラメーターのみを使用して関数を「バインド」し、残りのパラメーターは後で入力するように延期するプロセス。結果は、より少ないパラメーターを持つ別の関数です。
カリー化:一度に1つの引数しか「バインド」できない特殊な形式の部分機能アプリケーションです。結果は、パラメーターが1つ少ない別の関数です。
私が提示しようとしているコードは、カリー化が可能な部分機能アプリケーションですが、唯一の可能性ではありません。これは、上記のカリー化の実装に比べていくつかの利点があります(主に、部分機能アプリケーションであり、カリー化されていないためです)。
空の関数に適用する:
_auto sum0 = [](){return 0;};
std::cout << partial_apply(sum0)() << std::endl;
_
一度に複数の引数を適用する:
_auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;};
std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10
_
constexpr
コンパイル時のサポート_static_assert
_:
_static_assert(partial_apply(sum0)() == 0);
_
誤って引数を指定しすぎた場合に役立つエラーメッセージ:
_auto sum1 = [](int x){ return x;};
partial_apply(sum1)(1)(1);
_
エラー:static_assertが失敗しました「あまりにも多くの引数を適用しようとしています!」
上記の他の回答は、引数をバインドするラムダを返し、さらにラムダを返します。このアプローチは、その重要な機能を呼び出し可能なオブジェクトにラップします。 operator()
の定義により、内部ラムダを呼び出すことができます。可変個引数テンプレートを使用すると、行き過ぎをチェックできます。また、関数呼び出しの結果タイプへの暗黙的な変換関数を使用すると、結果を出力したり、オブジェクトをプリミティブと比較したりできます。
コード:
_namespace detail{
template<class F>
using is_zero_callable = decltype(std::declval<F>()());
template<class F>
constexpr bool is_zero_callable_v = std::experimental::is_detected_v<is_zero_callable, F>;
}
template<class F>
struct partial_apply_t
{
template<class... Args>
constexpr auto operator()(Args... args)
{
static_assert(sizeof...(args) == 0 || !is_zero_callable, "Attempting to apply too many arguments!");
auto bind_some = [=](auto... rest) -> decltype(myFun(args..., rest...))
{
return myFun(args..., rest...);
};
using bind_t = decltype(bind_some);
return partial_apply_t<bind_t>{bind_some};
}
explicit constexpr partial_apply_t(F fun) : myFun(fun){}
constexpr operator auto()
{
if constexpr (is_zero_callable)
return myFun();
else
return *this; // a callable
}
static constexpr bool is_zero_callable = detail::is_zero_callable_v<F>;
F myFun;
};
_
さらにいくつかの注意事項:
constexpr
ラムダサポートが必要なため、コードはC++ 17です C++ 17の場合いくつかのテスト:
_auto sum0 = [](){return 0;};
auto sum1 = [](int x){ return x;};
auto sum2 = [](int x, int y){ return x + y;};
auto sum3 = [](int x, int y, int z){ return x + y + z; };
auto sum10 = [](int a, int b, int c, int d, int e, int f, int g, int h, int i, int j){return a+b+c+d+e+f+g+h+i+j;};
std::cout << partial_apply(sum0)() << std::endl; //0
static_assert(partial_apply(sum0)() == 0, "sum0 should return 0");
std::cout << partial_apply(sum1)(1) << std::endl; // 1
std::cout << partial_apply(sum2)(1)(1) << std::endl; // 2
std::cout << partial_apply(sum3)(1)(1)(1) << std::endl; // 3
static_assert(partial_apply(sum3)(1)(1)(1) == 3, "sum3 should return 3");
std::cout << partial_apply(sum10)(1)(1,1)(1,1,1)(1,1,1,1) << std::endl; // 10
//partial_apply(sum1)(1)(1); // fails static assert
auto partiallyApplied = partial_apply(sum3)(1)(1);
std::function<int(int)> finish_applying = partiallyApplied;
std::cout << std::boolalpha << (finish_applying(1) == 3) << std::endl; // true
auto plus2 = partial_apply(sum3)(1)(1);
std::cout << std::boolalpha << (plus2(1) == 3) << std::endl; // true
std::cout << std::boolalpha << (plus2(3) == 5) << std::endl; // true
_
可変個引数テンプレートを使用したカリー化も実装しました(ジュリアンの回答を参照)。ただし、再帰やstd::function
は使用しませんでした。注:これは、いくつかのC++ 14機能を使用します。
提供されている例(main
関数)は実際にはコンパイル時に実行され、カリー化メソッドがコンパイラーによる本質的な最適化よりも優先されないことを証明しています。
コードはここにあります: https://Gist.github.com/Garciat/c7e4bef299ee5c607948
このヘルパーファイルを使用: https://Gist.github.com/Garciat/cafe27d04cfdff0e891e
コードにはまだ(多くの)作業が必要ですが、すぐに完了する場合とできない場合があります。いずれにせよ、私は将来の参考のためにこれをここに投稿します。
リンクが死んだ場合のコードの投稿(死ぬべきではありませんが):
#include <type_traits>
#include <Tuple>
#include <functional>
#include <iostream>
// ---
template <typename FType>
struct function_traits;
template <typename RType, typename... ArgTypes>
struct function_traits<RType(ArgTypes...)> {
using arity = std::integral_constant<size_t, sizeof...(ArgTypes)>;
using result_type = RType;
template <size_t Index>
using arg_type = typename std::Tuple_element<Index, std::Tuple<ArgTypes...>>::type;
};
// ---
namespace details {
template <typename T>
struct function_type_impl
: function_type_impl<decltype(&T::operator())>
{ };
template <typename RType, typename... ArgTypes>
struct function_type_impl<RType(ArgTypes...)> {
using type = RType(ArgTypes...);
};
template <typename RType, typename... ArgTypes>
struct function_type_impl<RType(*)(ArgTypes...)> {
using type = RType(ArgTypes...);
};
template <typename RType, typename... ArgTypes>
struct function_type_impl<std::function<RType(ArgTypes...)>> {
using type = RType(ArgTypes...);
};
template <typename T, typename RType, typename... ArgTypes>
struct function_type_impl<RType(T::*)(ArgTypes...)> {
using type = RType(ArgTypes...);
};
template <typename T, typename RType, typename... ArgTypes>
struct function_type_impl<RType(T::*)(ArgTypes...) const> {
using type = RType(ArgTypes...);
};
}
template <typename T>
struct function_type
: details::function_type_impl<typename std::remove_cv<typename std::remove_reference<T>::type>::type>
{ };
// ---
template <typename Args, typename Params>
struct apply_args;
template <typename HeadArgs, typename... Args, typename HeadParams, typename... Params>
struct apply_args<std::Tuple<HeadArgs, Args...>, std::Tuple<HeadParams, Params...>>
: std::enable_if<
std::is_constructible<HeadParams, HeadArgs>::value,
apply_args<std::Tuple<Args...>, std::Tuple<Params...>>
>::type
{ };
template <typename... Params>
struct apply_args<std::Tuple<>, std::Tuple<Params...>> {
using type = std::Tuple<Params...>;
};
// ---
template <typename TupleType>
struct is_empty_Tuple : std::false_type { };
template <>
struct is_empty_Tuple<std::Tuple<>> : std::true_type { };
// ----
template <typename FType, typename GivenArgs, typename RestArgs>
struct currying;
template <typename FType, typename... GivenArgs, typename... RestArgs>
struct currying<FType, std::Tuple<GivenArgs...>, std::Tuple<RestArgs...>> {
std::Tuple<GivenArgs...> given_args;
FType func;
template <typename Func, typename... GivenArgsReal>
constexpr
currying(Func&& func, GivenArgsReal&&... args) :
given_args(std::forward<GivenArgsReal>(args)...),
func(std::move(func))
{ }
template <typename... Args>
constexpr
auto operator() (Args&&... args) const& {
using ParamsTuple = std::Tuple<RestArgs...>;
using ArgsTuple = std::Tuple<Args...>;
using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
using CanExecute = is_empty_Tuple<RestArgsPrime>;
return apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...);
}
template <typename... Args>
constexpr
auto operator() (Args&&... args) && {
using ParamsTuple = std::Tuple<RestArgs...>;
using ArgsTuple = std::Tuple<Args...>;
using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
using CanExecute = is_empty_Tuple<RestArgsPrime>;
return std::move(*this).apply(CanExecute{}, std::make_index_sequence<sizeof...(GivenArgs)>{}, std::forward<Args>(args)...);
}
private:
template <typename... Args, size_t... Indices>
constexpr
auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) const& {
using ParamsTuple = std::Tuple<RestArgs...>;
using ArgsTuple = std::Tuple<Args...>;
using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
using CurryType = currying<FType, std::Tuple<GivenArgs..., Args...>, RestArgsPrime>;
return CurryType{ func, std::get<Indices>(given_args)..., std::forward<Args>(args)... };
}
template <typename... Args, size_t... Indices>
constexpr
auto apply(std::false_type, std::index_sequence<Indices...>, Args&&... args) && {
using ParamsTuple = std::Tuple<RestArgs...>;
using ArgsTuple = std::Tuple<Args...>;
using RestArgsPrime = typename apply_args<ArgsTuple, ParamsTuple>::type;
using CurryType = currying<FType, std::Tuple<GivenArgs..., Args...>, RestArgsPrime>;
return CurryType{ std::move(func), std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)... };
}
template <typename... Args, size_t... Indices>
constexpr
auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) const& {
return func(std::get<Indices>(given_args)..., std::forward<Args>(args)...);
}
template <typename... Args, size_t... Indices>
constexpr
auto apply(std::true_type, std::index_sequence<Indices...>, Args&&... args) && {
return func(std::get<Indices>(std::move(given_args))..., std::forward<Args>(args)...);
}
};
// ---
template <typename FType, size_t... Indices>
constexpr
auto curry(FType&& func, std::index_sequence<Indices...>) {
using RealFType = typename function_type<FType>::type;
using FTypeTraits = function_traits<RealFType>;
using CurryType = currying<FType, std::Tuple<>, std::Tuple<typename FTypeTraits::template arg_type<Indices>...>>;
return CurryType{ std::move(func) };
}
template <typename FType>
constexpr
auto curry(FType&& func) {
using RealFType = typename function_type<FType>::type;
using FTypeArity = typename function_traits<RealFType>::arity;
return curry(std::move(func), std::make_index_sequence<FTypeArity::value>{});
}
// ---
int main() {
auto add = curry([](int a, int b) { return a + b; });
std::cout << add(5)(10) << std::endl;
}
これらのリンクは関連しています:
ウィキペディアのラムダ計算ページには、カリー化の明確な例があります
http://en.wikipedia.org/wiki/Lambda_calculus#Motivation
このペーパーでは、C/C++でのカリー化について説明します。
http://asg.unige.ch/site/papers/Dami91a.pdf