code

C ++의 함수형 프로그래밍.

codestyles 2020. 11. 22. 19:47
반응형

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.


2 요소 솔루션을 가져와 다른 람다로 래핑하여 확장하십시오.

당신이 얻을 람다 반환 할 때문에 double그리고 반환 double의 '추가 (λ)를, 당신이 할 필요가 반환 람다 그 (람다를 다른 기능으로 현재의 반환 유형을 포장하고 현재에 중첩 된 람다를 추가하는 것입니다 ) :

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

함수 ffunctor , 즉 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;

다음은 *thisfrom에 대한 참조를 반환하는 약간 다른 접근 방식 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


이것은 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(:)루프가 우리 폴더의를 이해하지 않습니다 operator T(). 약간의 작업으로 해결할 수 있지만 .get()더 쉽습니다.


라이브러리 사용에 개방적이라면 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
}

이 모든 대답은 매우 복잡해 보입니다.

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

does exactly what you want, and it works in C++11, unlike many or perhaps most other answers here.

Note that it does not use std::function which incurs a performance penalty, and indeed, it can likely be inlined in many cases.


Here is a state pattern singleton inspired approach using operator() to change state.

Edit: Exchanged the unnecessary assignment for an initialization.

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

참고URL : https://stackoverflow.com/questions/43783517/functional-programming-in-c-implementing-fabc

반응형