Функциональное программирование на C++. Осуществление f (a) (b) (c)

Я изучал основы функционального программирования на C++. Я пытаюсь сделать функцию f(a)(b)(c), что вернет a + b + c. Я успешно реализовал функцию f(a)(b) который возвращает a + b. Вот код для него:

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

Я просто не могу понять, как реализовать функцию f(a)(b)(c) который, как я уже говорил, должен вернуться a + b + c.

9 ответов


просто возьмите решение 2 elements и разверните его, обернув его другой лямбдой.

так как вы хотите вернуть лямбду, которая получает double и возвращает doubles' addition lambda, все, что вам нужно сделать, это обернуть текущий тип возврата другой функцией и добавить вложенную лямбду в текущую (лямбда, которая возвращает лямбду):

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

  • As @Ðan отметил, вы можете пропустить 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 возвратить функтор, т. е. объект, который реализует operator(). Вот один из способов сделать это:

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

версия шаблона

вы даже можете написать шаблонную версию, которая позволит компилятору вывести тип. Попробуйте!--22-->здесь.

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;

здесь немного другой подход, который возвращает ссылку на *this С operator(), поэтому у вас нет никаких копий, плавающих вокруг. Это очень простая реализация функтора, который хранит состояние и левые складки рекурсивно на себе:

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

жить на Coliru


это не 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); };
}

это объединяет первые два списка аргументов в один 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

см. оба в действии здесь.


Я собираюсь играть.

вы хотите сделать складку Карри над добавлением. Мы могли бы решить эту проблему, или мы могли бы решить класс проблем, которые включают это.

Итак, во-первых, дополнение:

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(:) loops не понимает нашу папку operator T(). Мы можем исправить это с помощью небольшой работы, но .get() легче.


Если вы открыты для использования библиотеки, это очень легко в 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);

и затем использовать его так, как вы хотите:

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 что влечет за собой штраф за производительность, и действительно, во многих случаях он может быть встроен.


вот шаблон состояния синглтон вдохновил подход с использованием operator() изменить состояние.

Edit: обменял ненужное назначение на инициализацию.

#include<iostream>
class adder{
private:
  adder(double a)val(a){}
  double val = 0.0;
  static adder* mInstance;
public:
  adder operator()(double a){
    val += a;
    return *this;}
  static adder add(double a){
    if(mInstance) delete mInstance;
    mInstance = new adder(a);
    return *mInstance;}
  double get(){return val;}
};
adder* adder::mInstance = 0;
int main(){
  adder a = adder::add(1.0)(2.0)(1.0);
  std::cout<<a.get()<<std::endl;
  std::cout<<adder::add(1.0)(2.0)(3.0).get()<<std::endl;
  return 0;
}