Функциональное программирование на С++. Реализация f (a) (b) (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
.
Ответы
Ответ 1
Просто возьмите 2-х элементное решение и разверните его, обернув его другой лямбдой.
Поскольку вы хотите вернуть лямбду, которая получает double
и возвращает lambda с добавлением 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;
};
};
};
}
Ответ 2
Вы можете сделать это, если ваша функция f
возвращает functor, то есть объект, который реализует 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;
}
Версия шаблона
Вы даже можете написать шаблонную версию, которая позволит компилятору вывести тип. Попробуйте здесь.
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;
Ответ 3
Вот несколько иной подход, который возвращает ссылку на *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);
}
Live on Coliru
Ответ 4
Это не f(a)(b)(c)
, а скорее curry(f)(a)(b)(c)
. Мы завершаем f
таким образом, чтобы каждый дополнительный аргумент возвращал другой curry
или действительно вызывал функцию с нетерпением. Это С++ 17, но может быть реализован на С++ 11 с кучей дополнительной работы.
Обратите внимание, что это решение для currying function - это впечатление, которое я получил от вопроса, - а не решение для сгибания по двоичной функции.
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
Ответ 5
Самый простой способ, которым я могу это сделать, - определить 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
Смотрите как в действии здесь.
Ответ 6
Я буду играть.
Вы хотите сделать сальдо в карри. Мы могли бы решить эту проблему, или мы могли бы решить класс проблем, которые включают это.
Итак, во-первых, добавление:
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()
проще.
Ответ 7
Если вы открыты для использования библиотек, это действительно легко в Boost 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
}
Ответ 8
Все эти ответы кажутся ужасно сложными.
auto f = [] (double a) {
return [=] (double b) {
return [=] (double c) {
return a + b + c;
};
};
};
делает именно то, что вы хотите, и работает на С++ 11, в отличие от многих или, возможно, большинства других ответов здесь.
Обратите внимание, что он не использует std::function
, который несет штраф за производительность, и действительно, он может быть во многих случаях встроен.
Ответ 9
Вот пример с использованием однострочного шаблона с использованием operator()
для изменения состояния.
Изменить: Изменено ненужное назначение для инициализации.
#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;
}