KoreanFoodie's Study
[C++ 함수형 프로그래밍] 함수형 합성 (C++ 에서 함수 합성하기) 본문
함수형 프로그래밍 패러다임에 대해 알아보며 이를 C++ 를 이용한 소프트웨어 개발에 어떻게 적용하면 좋을지 알아보겠습니다.
핵심 :
1. 함수(람다)는 값이다. 고로 함수는 인자로 전달할 수 있고, 람다를 반환할 수 있으며 람다도 람다를 반환할 수 있다.
2. 순수 함수의 조합을 통한 설계는 불변성을 보장한다. 우리는 간단한 람다를 조합하여 고차원의 합성 함수를 만들어낼 수 있다.
3. 함수형 합성을 통해 중복을 효과적으로 제거할 수 있다. 또한 여러 인자를 가진 람다도 하나의 인자와 캡쳐된 값들을 가진 다수의 람다로 분해할 수 있다.
함수형 합성
우리는 고등학교 시간에 함수 합성에 대해 배웠다(혹은 중학교일수도 있다).
놀랍게도, 우리는 C++ 에서 동일한 작업을 할 수 있다. 😛 우리는 이제 람다를 합성할 것이다.
template <typename F, typename G>
auto compose(F f, G g)
{
return [=](auto value) { return f(g(value)); };
}
위 템플릿 함수를 한번 활용해 보자.
우리는 인자에 값을 1 더하고, 제곱을 하는 함수를 만들어 볼 것이다.
auto increment = [](auto value) {return value + 1; };
auto square = [](auto value) {return value * value; };
int value = 1;
auto incrementAndSquare = compose(square, increment);
incrementAndSquare(value);
cout << "Result : " << incrementAndSquare(value) << endl;
그리고 함수형 합성에는 교환 법칙이 성립하지 않는다는 것도 명심하자 😀
그런데 위의 예시는 인자를 1개만 받고 있다. 다수의 인자를 가진 함수를 조합하려면 어떻게 해야 할까?
만약 두 수를 각각 1씩 증가시키고 곱하거나, 곱하고 1씩 증가시키는 것을 원한다고 가정해 보자. 그럼 각 상황에 맞게, compose 를 약간 변형해야 한다. 일단 증가 및 곱셉 함수는 다음과 같을 것이다 :
auto increment = [](auto i) {return i + 1; };
auto multiply = [](auto i, auto j) {return i * j; };
그럼 합성을 위한 함수는 다음과 같이 작성할 수 있다 :
// 각각을 1씩 증가시키고, 곱하는 합성을 위한 Lambda
template <typename F, typename G>
auto compose12(F f, G g)
{
return [=](auto i, auto j) {return g(f(i), f(j)); };
};
// 두 수를 곱하고 1을 증가시키는 합성을 위한 Lambda
template <typename F, typename G>
auto compose21(F f, G g)
{
return [=](auto i, auto j) {return f(g(i, j)); };
};
실제로 예제를 돌려보면, 다음과 같은 결과를 확인할 수 있다 😉
int i = 1, j = 2;
// (1 + 1) * (2 + 1) = 6
auto incrementAndMultiply = compose12(increment, multiply);
cout << "Result : " << incrementAndMultiply(i, j) << endl;
// (1 * 2) + 1 = 3;
auto multiplyAndIncrement = compose21(increment, multiply);
cout << "Result : " << multiplyAndIncrement(i, j) << endl;
그런데 인자의 갯수에 따라 각 조합마다 compose 함수를 만드는 것은 매우 지치는 작업을 것이다. 다행히도, 람다를 이용하여 여러 인자를 가진 함수를 더 적은 인수를 받는 람다로 분해할 수 있다! 😊
다수의 인자를 가진 함수 분해
앞서 정의했던 multiply 함수를 단일 인자를 받는 람다 두 개로 한 번 쪼개보자. 다음과 같이 사용할 수 있을 것이다.
auto multiplyDecomposed = [](auto first)
{
return [=](auto second) {return first * second; };
};
위 변환을 좀 더 범용적으로 적용한다고 가정하면, 아래와 같은 템플릿 클래스가 탄생한다.
template <typename F>
auto decomposeToOneParam(F f)
{
return [=](auto first)
{
return [=](auto second)
{
return f(first, second);
};
};
};
이 함수를 사용하면, 우리가 맨 처음에 정의했던 단일 인자를 받는 compose 함수를 활용할 수 있게 된다.
auto multiplyAndIncrement2 = [](int i, int j)
{
return compose(increment, decomposeToOneParam(multiply)(i))(j);
};
cout << "Result : " << multiplyAndIncrement2(i, j) << endl;
이제 우리는 인자를 2개 받으면서 기존의 compose 를 활용하는 람다를 만든 것이다!
그렇다면 이번엔 반대로, 두 인자를 증가시킨 후 곱하고 싶다면 어떻게 해야 할까?
결론만 말하자면 이미 구현해 놓은 함수를 활용해 다음과 같이 표현할 수 있다! 😮
auto incrementAndMultiply2 = [](int i, int j)
{
return compose(decomposeToOneParam(multiply), increment)(i)(increment(j));
};
cout << "Result : " << incrementAndMultiply2(i, j) << endl;
더 나아가서, 우리가 만든 함수를 좀 더 일반화 할 수 있다.
먼저 곱한 값에 1을 더하는 함수인 multiplyAndIncrement 를 일반화 해보면 아래와 같이 표현할 수 있다.
template <typename F, typename G>
auto ComposeWithTwoParams(F f, G g)
{
return [=](int i, int j)
{
return compose(f, decomposeToOneParam(g)(i))(j);
};
};
/* ... */
// (1 * 2) + 1 = 3;
auto multiplyAndIncrement = ComposeWithTwoParams(increment, multiply);
cout << "Result : " << multiplyAndIncrement(i, j) << endl;
위와 같이 표현할 수 있으며, 마찬가지로 각 수에 1을 더한 것을 곱하는 함수인 incrementAndMultiply 를 일반화 하면...
template <typename F, typename G>
auto ComposeWithFunctionCallTwoParams(F f, G g)
{
return [=](int i, int j)
{
return compose(decomposeToOneParam(g), f)(i)(f(j));
};
}
/* ... */
// (1 + 1) * (2 + 1) = 6
auto incrementAndMultiply = ComposeWithFunctionCallTwoParams(increment, multiply);
cout << "Result : " << incrementAndMultiply(i, j) << endl;
로 표현할 수 있다.
일단 이번 글에서 예제 분석은 여기서 마무리하고, 다음 글에서는 파셜 애플리케이션과 커링을 다루도록 하겠다!
'R & D > Software Engineering' 카테고리의 다른 글
[C++ 함수형 프로그래밍] 함수로 설계하기 (feat. 틱택토와 STL) (0) | 2023.09.28 |
---|---|
[C++ 함수형 프로그래밍] 파셜 애플리케이션과 커링 (0) | 2023.09.28 |
[C++ 함수형 프로그래밍] 순수 함수와 람다 (0) | 2023.09.27 |
UML (클래스 다이어그램, 시퀀스 다이어그램) 간단 정리 (0) | 2022.10.14 |
[책 리뷰] 객체 지향의 사실과 오해 리뷰 (0) | 2022.08.29 |