私は、C++を使用した関数型プログラミングの基礎に取り組んできました。 _a + b + c
_を返す関数f(a)(b)(c)
を作成しようとしています。 a + bを返す関数f(a)(b)
を正常に実装しました。コードは次のとおりです。
_std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
_
前に述べたように_a + b + c
_を返す関数f(a)(b)(c)
を実装する方法がわかりません。
別のラムダでラップすることで、2つの要素のソリューションを取得して拡張するだけです。
double
を取得してdouble
sの追加ラムダを返すラムダを返したいので、必要なことは、現在の戻り値の型を別の関数でラップし、ネストされたラムダを追加することですあなたの現在のもの(ラムダを返すラムダ)に:
_std::function<std::function<double(double)>(double)> plus3 (double a){
return [a] (double b) {
return [a, b] (double c) {
return a + b + c;
};
};
}
_
@Ðаnのように、std::function<std::function<double(double)>(double)>
をスキップしてauto
を取得できます:
_auto plus3 (double a){
return [a] (double b) {
return [a, b] (double c) { return a + b + c; };
};
}
_
より深くネストされたラムダを使用して、要素の数ごとにこの構造を拡張できます。 4つの要素のデモンストレーション:
_auto plus4 (double a){
return [a] (double b) {
return [a, b] (double c) {
return [a, b, c] (double d) {
return a + b + c + d;
};
};
};
}
_
関数f
が functor 、つまりoperator()
を実装するオブジェクトを返すようにすることで、それを行うことができます。これを行う1つの方法を次に示します。
struct sum
{
double val;
sum(double a) : val(a) {}
sum operator()(double a) { return val + a; }
operator double() const { return val; }
};
sum f(double a)
{
return a;
}
int main()
{
std::cout << f(1)(2)(3)(4) << std::endl;
}
コンパイラに型を推測させるテンプレートバージョンを作成することもできます。試してみてください こちら 。
template <class T>
struct sum
{
T val;
sum(T a) : val(a) {}
template <class T2>
auto operator()(T2 a) -> sum<decltype(val + a)> { return val + a; }
operator T() const { return val; }
};
template <class T>
sum<T> f(T a)
{
return a;
}
この例では、T
は最終的にdouble
に解決されます。
std::cout << f(1)(2.5)(3.1f)(4) << std::endl;
少し異なるアプローチがあります。これは、operator()
から*this
への参照を返すため、コピーが浮かんでいません。これは、状態と左フォールドをそれ自体に再帰的に保存するファンクターの非常に単純な実装です。
#include <iostream>
template<typename T>
class Sum
{
T x_{};
public:
Sum& operator()(T x)
{
x_ += x;
return *this;
}
operator T() const
{
return x_;
}
};
int main()
{
Sum<int> s;
std::cout << s(1)(2)(3);
}
これはf(a)(b)(c)
ではなく、curry(f)(a)(b)(c)
です。追加の各引数が別のf
を返すか、実際に関数を積極的に呼び出すように、curry
をラップします。これはC++ 17ですが、C++ 11に追加の作業を追加して実装できます。
これは関数をカリー化するための解決策であることに注意してください-これは質問から得た印象です-バイナリ関数を折り返すための解決策ではありません。
template <class F>
auto curry(F f) {
return [f](auto... args) -> decltype(auto) {
if constexpr(std::is_invocable<F&, decltype(args)...>{}) {
return std::invoke(f, args...);
}
else {
return curry([=](auto... new_args)
-> decltype(std::invoke(f, args..., new_args...))
{
return std::invoke(f, args..., new_args...);
});
}
};
}
簡潔にするため、転送の参照はスキップしました。使用例は次のとおりです。
int add(int a, int b, int c) { return a+b+c; }
curry(add)(1,2,2); // 5
curry(add)(1)(2)(2); // also 5
curry(add)(1, 2)(2); // still the 5th
curry(add)()()(1,2,2); // FIVE
auto f = curry(add)(1,2);
f(2); // i plead the 5th
これを行うために考えられる最も簡単な方法は、plus3()
の観点からplus2()
を定義することです。
_std::function<double(double)> plus2(double a){
return[a](double b){return a + b; };
}
auto plus3(double a) {
return [a](double b){ return plus2(a + b); };
}
_
これにより、最初の2つの引数リストが単一のarglistに結合され、plus2()
の呼び出しに使用されます。そうすることで、最小限の繰り返しで既存のコードを再利用でき、将来簡単に拡張できます。 plusN()
は、plusN-1()
を呼び出すラムダを返すだけで、plus2()
に達するまで、前の関数に呼び出しを渡します。次のように使用できます。
_int main() {
std::cout << plus2(1)(2) << ' '
<< plus3(1)(2)(3) << '\n';
}
// Output: 3 6
_
単にインラインで呼び出すことを考えると、これを簡単に関数テンプレートに変えることができ、追加の引数のバージョンを作成する必要がなくなります。
_template<int N>
auto plus(double a);
template<int N>
auto plus(double a) {
return [a](double b){ return plus<N - 1>(a + b); };
}
template<>
auto plus<1>(double a) {
return a;
}
int main() {
std::cout << plus<2>(1)(2) << ' '
<< plus<3>(1)(2)(3) << ' '
<< plus<4>(1)(2)(3)(4) << ' '
<< plus<5>(1)(2)(3)(4)(5) << '\n';
}
// Output: 3 6 10 15
_
アクションで両方を参照してください こちら 。
遊びに行きます。
カレーフォールドオーバー加算を行います。この1つの問題を解決することも、これを含む問題のクラスを解決することもできます。
したがって、まず、追加:
_auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };
_
これは加算の概念を非常によく表しています。
今、折りたたみ:
_template<class F, class T>
struct folder_t {
F f;
T t;
folder_t( F fin, T tin ):
f(std::move(fin)),
t(std::move(tin))
{}
template<class Lhs, class Rhs>
folder_t( F fin, Lhs&&lhs, Rhs&&rhs):
f(std::move(fin)),
t(
f(std::forward<Lhs>(lhs), std::forward<Rhs>(rhs))
)
{}
template<class U>
folder_t<F, std::result_of_t<F&(T, U)>> operator()( U&& u )&&{
return {std::move(f), std::move(t), std::forward<U>(u)};
}
template<class U>
folder_t<F, std::result_of_t<F&(T const&, U)>> operator()( U&& u )const&{
return {f, t, std::forward<U>(u)};
}
operator T()&&{
return std::move(t);
}
operator T() const&{
return t;
}
};
_
シード値とTを受け取り、連鎖を許可します。
_template<class F, class T>
folder_t<F, T> folder( F fin, T tin ) {
return {std::move(fin), std::move(tin)};
}
_
今、それらを接続します。
_auto adder = folder(add, 0);
std::cout << adder(2)(3)(4) << "\n";
_
他の操作にfolder
を使用することもできます。
_auto append = [](auto vec, auto element){
vec.Push_back(std::move(element));
return vec;
};
_
つかいます:
_auto appender = folder(append, std::vector<int>{});
for (int x : appender(1)(2)(3).get())
std::cout << x << "\n";
_
ライブの例 。
.get()
ループはフォルダーのfor(:)
を理解しないため、ここでoperator T()
を呼び出す必要があります。少しの作業で修正できますが、.get()
の方が簡単です。
ライブラリの使用にオープンである場合、これは Boost's Hana で非常に簡単です。
double plus4_impl(double a, double b, double c, double d) {
return a + b + c + d;
}
constexpr auto plus4 = boost::hana::curry<4>(plus4_impl);
そして、それを使用するのはあなたが望むとおりです:
int main() {
std::cout << plus4(1)(1.0)(3)(4.3f) << '\n';
std::cout << plus4(1, 1.0)(3)(4.3f) << '\n'; // you can also do up to 4 args at a time
}
これらの答えはすべて非常に複雑に見えます。
auto f = [] (double a) {
return [=] (double b) {
return [=] (double c) {
return a + b + c;
};
};
};
あなたが望むことを正確に行い、ここでの多くのまたはおそらく他のほとんどの答えとは異なり、C++ 11で動作します。
パフォーマンスの低下を招くstd::function
を使用しないことに注意してください。実際、多くの場合インライン化される可能性があります。