web-dev-qa-db-ja.com

C ++での関数型プログラミング。実装f(a)(b)(c)

私は、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)を実装する方法がわかりません。

63
Gigaxel

別のラムダでラップすることで、2つの要素のソリューションを取得して拡張するだけです。

doubleを取得してdoublesの追加ラムダを返すラムダを返したいので、必要なことは、現在の戻り値の型を別の関数でラップし、ネストされたラムダを追加することですあなたの現在のもの(ラムダを返すラムダ)に:

_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;
                };
            };
        };
    }
    _
58
Uriel

関数ffunctor 、つまり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;
115
Jonas

少し異なるアプローチがあります。これは、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);
}

Live on Coliru

30
vsoftco

これは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
15
Barry

これを行うために考えられる最も簡単な方法は、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
_

アクションで両方を参照してください こちら

12
Justin Time

遊びに行きます。

カレーフォールドオーバー加算を行います。この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
}
10
Justin

これらの答えはすべて非常に複雑に見えます。

auto f = [] (double a) {
    return [=] (double b) {
        return [=] (double c) {
            return a + b + c;
        };
    };
};

あなたが望むことを正確に行い、ここでの多くのまたはおそらく他のほとんどの答えとは異なり、C++ 11で動作します。

パフォーマンスの低下を招くstd::functionを使用しないことに注意してください。実際、多くの場合インライン化される可能性があります。

4
Tom Swirly