web-dev-qa-db-fra.com

Programmation fonctionnelle en C ++. Mise en œuvre f(a)(b)(c)

Je suis entré dans les bases de la programmation fonctionnelle avec C++. J'essaie de créer une fonction f(a)(b)(c) qui renverra a + b + c. J'ai implémenté avec succès la fonction f(a)(b) qui renvoie a + b. Voici le code pour cela:

std::function<double(double)> plus2(double a){
    return[a](double b){return a + b; };
}

Je n'arrive pas à comprendre comment implémenter la fonction f(a)(b)(c) qui, comme je l'ai indiqué précédemment, devrait renvoyer a + b + c.

63
Gigaxel

Prenez simplement votre solution à 2 éléments et développez-la en l’enveloppant avec un autre lambda.

Puisque vous voulez renvoyer un lambda qui obtient un double et renvoie un lambda addition de doubles, il vous suffit d’envelopper votre type de retour actuel avec une autre fonction et d’ajouter un lambda imbriqué. dans votre version actuelle (un lambda qui retourne un lambda):

std::function<std::function<double(double)>(double)> plus3 (double a){
    return [a] (double b) {
        return [a, b] (double c) {
            return a + b + c;
        };
    };
}

  • Comme @ Ðаn, vous pouvez ignorer la std::function<std::function<double(double)>(double)> et vous entendre avec auto:

    auto plus3 (double a){
        return [a] (double b) {
            return [a, b] (double c) { return a + b + c; };
        };
    }
    
  • Vous pouvez développer cette structure pour chaque nombre d'éléments à l'aide de lambdas imbriquées plus profondes. Démonstration pour 4 éléments:

    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

Vous pouvez le faire en faisant en sorte que votre fonction f renvoie un foncteur , c’est-à-dire un objet qui implémente operator(). Voici une façon de le faire:

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;
}

Exemple

Lien

int main()
{
    std::cout << f(1)(2)(3)(4) << std::endl;
}

Version du modèle

Vous pouvez même écrire une version basée sur un modèle qui permettra au compilateur de déduire le type. Essayez-le ici .

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;
}

Exemple

Dans cet exemple, T finira par être résolu en double:

std::cout << f(1)(2.5)(3.1f)(4) << std::endl;
115
Jonas

Voici une approche légèrement différente, qui renvoie une référence à *this À partir de operator(), de sorte que vous ne disposez d'aucune copie flottante. Il s’agit d’une implémentation très simple d’un foncteur qui stocke l’état et les replis gauches récursivement sur lui-même:

#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

Ce n'est pas f(a)(b)(c) mais plutôt curry(f)(a)(b)(c). Nous encapsulons f de telle sorte que chaque argument supplémentaire retourne un autre curry ou appelle réellement la fonction avec impatience. C'est C++ 17, mais peut être implémenté en C++ 11 avec beaucoup de travail supplémentaire.

Notez que ceci est une solution pour currying une fonction - qui est l'impression que j'ai tirée de la question - et non une solution pour replier une fonction binaire.

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...);
                });
        }
    };  
}

J'ai sauté des références de transmission par souci de brièveté. Exemple d'utilisation serait:

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

Le moyen le plus simple auquel je puisse penser est de définir plus3() en termes de 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); };
}

Cela combine les deux premières listes d'arguments dans une seule liste, qui est utilisée pour appeler plus2(). Cela nous permet de réutiliser notre code préexistant avec un minimum de répétition et peut facilement être étendu à l'avenir; plusN() a juste besoin de renvoyer un lambda qui appelle plusN-1(), qui transmettra l'appel à la fonction précédente tour à tour, jusqu'à atteindre plus2(). Il peut être utilisé comme suit:

int main() {
    std::cout << plus2(1)(2)    << ' '
              << plus3(1)(2)(3) << '\n';
}
// Output: 3 6

Étant donné que nous appelons simplement en ligne, nous pouvons facilement transformer cela en un modèle de fonction, ce qui élimine le besoin de créer des versions pour des arguments supplémentaires.

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

Voir les deux en action ici .

12
Justin Time

Je vais jouer.

Vous voulez faire un pli au curry plus. Nous pourrions résoudre ce problème ou résoudre une classe de problèmes qui incluent ce problème.

Donc, d'abord, addition:

auto add = [](auto lhs, auto rhs){ return std::move(lhs)+std::move(rhs); };

Cela exprime assez bien le concept d'addition.

Maintenant, pliant:

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;
  }
};

Il prend une valeur de départ et un T, puis permet l'enchaînement.

template<class F, class T>
folder_t<F, T> folder( F fin, T tin ) {
  return {std::move(fin), std::move(tin)};
}

Maintenant nous les connectons.

auto adder = folder(add, 0);
std::cout << adder(2)(3)(4) << "\n";

Nous pouvons également utiliser folder pour d'autres opérations:

auto append = [](auto vec, auto element){
  vec.Push_back(std::move(element));
  return vec;
};

Utilisation:

auto appender = folder(append, std::vector<int>{});
for (int x : appender(1)(2)(3).get())
    std::cout << x << "\n";

Exemple en direct .

Nous devons appeler .get() ici parce que les boucles for(:) ne comprennent pas le dossier operator T() de notre dossier. Nous pouvons résoudre ce problème avec un peu de travail, mais .get() est plus facile.

Si vous êtes ouvert à l'utilisation de bibliothèques, c'est très facile en Hana de Boost :

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);

Et puis l'utiliser est juste comme vous le désirez:

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

Toutes ces réponses semblent terriblement compliquées.

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

fait exactement ce que vous voulez, et cela fonctionne en C++ 11, contrairement à beaucoup, voire à la plupart des autres réponses ici.

Notez qu'il n'utilise pas std::function qui entraîne une pénalité de performance et qui, en réalité, peut probablement être doublée dans de nombreux cas.

4
Tom Swirly