Advanced C++

[Functional C++] 함수형 프로그래밍 (3) 파셜 어플리케이션과 커링

로파이 2022. 10. 8. 00:21

파셜 어플리케이션과 커링에 대해 알아본다. C++ 17 기준을 요한다.

 

파셜 어플리케이션

 

N개의 인자를 가진 함수에서 하나의 인자를 바인딩(결정)하여 N-1개의 인자를 가진 함수를 만들어내는 것.

 

두 개의 인자를 가지는 add 함수에 첫번째 함수 인자를 결정하여 increment 함수를 만든다. 

auto add = [](const int first, const int second) { return first + second; };
//auto increment = partialApplication(add, 1);
// 위 코드는 다음과 동치이다.
auto increment = [](const int second) { return 1 + second; };

 

C++ 파셜 어플리케이션

 

기존 함수를 재사용하는 방법

TEST_CASE("Increments using manual partial application") {
    auto increment = [](const int value) { return add(value, 1); };
    CHECK_EQ(43, increment(42));
}

increment는 add 함수를 사용함으로써 파셜 어플리케이션을 만든다.

 

bind 함수 (functional 헤더)

bind 함수는 말 그대로 타겟 함수에 일부 인자들을 미리 결정하는 것이 가능하다.

using namespace std::placeholders;

TEST_CASE("Increments using bind") {
    auto increment = bind(add, 1, _1);

    CHECK_EQ(43, increment(42));
}

std::placeholders::_1, _2 와 같은 타입은 std::bind를 사용할 때 바인딩하고 나머지 결정 인수들을 표현한다.

 

일반 클래스 인스턴스의 멤버함수를 파셜 어플리케이션으로 만들기

class AddOperation {
private:
    int first;
    int second;

public:
    AddOperation(int first, int second)
        :
        first(first), second(second) {
    }

    int add() { return first + second; }
};

TEST_CASE("Bind member method") {
    AddOperation operation(41, 1);
    auto add41And1 = bind(&AddOperation::add, operation);
    CHECK_EQ(42, add41And1);
}

bind로 인스턴스를 넘긴다.

 

 

커링 Currying

N개의 인자를 가진 함수를 하나의 인자를 가진 N개 함수로 분해하는 과정을 커링이라고 하며 변수 캡처나 파셜 어플리케이션을 활용해 커링할 수 있다.

 

f(x1, x2, x3, ... , xn) = f(x1)(x2)...(xn)으로 표현 가능하다.

 

이를 일반화하여 n개의 인수를 가진 함수는 n-1개의 인수를 가진 함수와 1개의 인수를 가진 함수로 분해 가능하다.

f(x1, x2, ..., xn)
    = f(x1,...,xn-1)*f(xn)
    = f(x1,...,xn-2)*f(xn-1)*f(xn)
    = ...
    = f(x1)*f(x2)*...*f(xn-1)*f(xn)

 

하나씩 인수를 분해하는 과정을 람다와 bind를 통해서 만들 수 있다.

 

예를 들면 3개의 인수를 가진 함수는 2개의 인수를 가진 함수와 1개의 인수를 가진 함수로 분해한다.

auto simpleCurry3 = [](auto f) {
    return [f](auto x, auto y) { return bind(f, x, y, _1); };
};

다시 2개의 인수를 가진 함수를 1개의 인수를 가진 두 함수로 분해한다.

auto simpleCurry2 = [](auto f) {
    return [f](auto x) { return bind(f, x, _1); };
};

 

이제 3개의 인수를 가진 함수 addThree를 커링 시켜본다.

auto addThree = [](const int first, const int second, const int third) {
    return first + second + third;
};

TEST_CASE("Add three with partial application curry") {
    auto addThreeCurry = simpleCurry2(simpleCurry3(addThree));
    CHECK_EQ(6, curry3(addThree)(1)(2)(3));
};

 

각각 simpleCurry를 잘 감싼 currying 람다 함수를 통해 간단히 표현할 수 있다.

auto curry2 = [](auto f) {
    return simpleCurry2(f);
};

auto curry3 = [](auto f) {
    return curry2(simpleCurry3(f));
};

auto addThree = [](const int first, const int second, const int third) {
    return first + second + third;
};

TEST_CASE("Add three with partial application curry") {
    auto addThreeCurry = curry3(addThree);
    CHECK_EQ(6, curry3(addThree)(1)(2)(3));
};