KoreanFoodie's Study
[C++ 함수형 프로그래밍] 함수로 설계하기 (feat. 틱택토와 STL) 본문
함수형 프로그래밍 패러다임에 대해 알아보며 이를 C++ 를 이용한 소프트웨어 개발에 어떻게 적용하면 좋을지 알아보겠습니다.
핵심 :
1. 모든 프로그램은 순수 함수의 집합으로 표현될 수 있으며, 입력과 출력이 상호 작용하는 함수 집합으로 정의할 수 있다.
2. 입력과 출력 중심으로 사고하며, 중간 단계의 변환 과정을 차례대로 구현해 보자.
함수형으로 사고하기
모든 프로그램은 순수 함수의 집합으로 표현될 수 있으며, 입력과 출력이 상호 작용하는 함수 집합으로 정의할 수 있다.
함수를 설계할 때, 다음 스텝을 밟으면 함수를 간단하게 설계할 수 있다.
- 입력 데이터로부터 출발한다.
- 출력 데이터를 정의한다.
- 일련의 입력 데이터를 출력 데이터로 변환하는 각 단계인 변형 과정(순수 함수)를 정의한다.
사실 위 내용은 말로만 들어서는 감이 잘 안 올 수 있다. 그래서 예제를 곁들이면 참~ 좋은데... 책에서는 틱택토 게임을 만들면서 이 내용을 다뤘다.
하지만... 포스팅에서 모든 내용을 따라가며 설명하는 것은 너무 피곤한 일이다. 😖 그래서 개념적으로만 생각해 보자.
틱택토 게임에서 승자를 가려야 하는데, 그럼 일단 각 라인(행, 열, 대각선)이 한 문자로 가득차 있는지 체크해야 한다.
필요한 함수의 흐름을 생각하면 다음과 같다 :
1. filledWithX, filledWithO 가 필요하네?
->그러려면 한 줄을 체크하는 로직이 필요하다.
2. any_of 를 활용해서 전체 컬렉션을 순회해야겠다!
->any_of 를 순회할 때 각 요소에 대해 체크할 predicate 가 필요하네?
3. 캐릭터가 'O' 인지 'X' 인지 체크해야 하네?
-> equalChar(char val) 함수가 필요하네?
이런 식으로 꼬리에 꼬리를 물고 구현하면 될 것이다.
실제로 위와 같은 사고방식은 TDD(Test Driven Development; 테스트 주도 개발) 개발에도 잘 맞아 떨어진다. 빠르게 실패하고, 빠르게 고치자 😉
또한 위와 같은 식으로 여러 함수가 만들어 졌을 때, 각 쓰임새나 인자 타입별로 순수 함수들을 묶을 수 있을 것이다.
OOP 의 핵심이 객체간의 상호작용이라는 점을 기억해 보면, 성질이 비슷한 순수함수들을 하나의 클래스에 묶어 응집도를 올림으로써 OOP 방식의 철학을 지킬 수 있게 된다. 😝 OOP 와 함수형 패러다임은 서로 대립하는 관계가 아님을 기억하자!
마지막으로, 이번 글에서는 각 컬렉션을 다루는 데 유용한 여러 예제 코드를 소개하고 기록을 마치고자 한다 😅
// 전체 컬렉션 순회하며 fn 적용
auto AllOfCollection = [](const auto& collection, auto fn)
{
return all_of(collection.begin(), collection.end(), fn);
};
// transform 이용하여 변환 컬렉션 만들기
// 출력 타입이 입력 타입과 일치하지 않아도 된다는 것이 놀랍다!
template<typename DestType>
auto TransformAll = [](const auto& source, auto fn)
{
DestType result;
transform(source.begin(), source.end(), back_inserter(result), fn);
return result;
};
// 범위 구하기 (Range 개념은 C++ 20 에서 표준으로 채택되었다)
// iota 는 시작 인자부터 1씩 더해가며 컬렉션을 채워주는 함수이다
auto ToRange = [](const auto& collection)
{
vector<int> result(collection.size());
iota(begin(result), end(result), 0);
return result;
};
// 전체 컬렉션 중 조건을 만족하는 녀석이 있는지 체크
auto AnyOfCollection = [](const auto& collection, auto fn)
{
return any_of(collection.begin(), collection.end(), fn);
};
// 컬렉션이 문자열일 때, 문자열을 합치는 것 따위에 이용될 수 있다.
// string<vector> i{"1", "2", "3"} -> "123"
auto AccumulateAll = [](const auto& collection, auto fn)
{
return accumulate(collection.begin(), collection.end(), typename decltype(collection)::value_type(), fn);
};
// 전체 컬렉션 중 조건에 맞는 녀석을 반환
// 맞는 것을 찾지 못했을 경우, optional 을 이용해 에러를 체크한다.
auto FindInCollection = [](const auto& collection, auto fn)
{
auto result = find_if(collection.begin(), collection.end(), fn);
return (result == collection.end()) ? nullopt : optional(*result);
};
// 컬렉션 중 조건을 만족하는 것이 없는지 체크
auto NoneOf = [](const auto& collection, auto fn)
{
return none_of(collection.begin(), collection.end(), fn);
};
'R & D > Software Engineering' 카테고리의 다른 글
[C++ 함수형 프로그래밍] 퍼포먼스 최적화(메모이제이션과 꼬리 재귀, 병렬 연산) (0) | 2023.09.28 |
---|---|
[C++ 함수형 프로그래밍] 함수형 연산자를 활용한 중복 제거 (0) | 2023.09.28 |
[C++ 함수형 프로그래밍] 파셜 애플리케이션과 커링 (0) | 2023.09.28 |
[C++ 함수형 프로그래밍] 함수형 합성 (C++ 에서 함수 합성하기) (0) | 2023.09.28 |
[C++ 함수형 프로그래밍] 순수 함수와 람다 (0) | 2023.09.27 |
Comments