간결하고 읽기 쉬운 C++ 코드를 작성하는 데 도움이 됩니다.
훌륭한 코드는 대부분 자체적으로 문서화되어야 하지만 실제로 C++를 사용하는 동안 코드의 실제 본질을 방해하는 반복자나 손으로 작성한 루프와 같은 낮은 수준의 작업을 처리하게 될 수 있습니다.
FunctionalPlus는 코드 노이즈를 줄이고 한 번에 하나의 단일 추상화 수준만 처리하도록 지원하는 작은 헤더 전용 라이브러리입니다 . 코드의 간결성과 유지 관리 가능성을 높임으로써 장기적으로 생산성과 재미를 향상시킬 수 있습니다. 일반적으로 사용되는 제어 흐름을 반복해서 구현할 필요가 없도록 순수하고 사용하기 쉬운 기능을 제공함으로써 이러한 목표를 추구합니다.
숫자 목록이 있고 홀수 숫자에만 관심이 있다고 가정해 보겠습니다.
bool is_odd_int ( int x) { return x % 2 != 0 ; }
int main ()
{
typedef vector< int > Ints;
Ints values = { 24 , 11 , 65 , 44 , 80 , 18 , 73 , 90 , 69 , 18 };
// todo: get odd numbers from values ...
}
목표를 달성할 수 있는 가능성은 다양합니다. 그 중 일부는 다음과 같습니다:
Ints odds;
for ( int x : values)
{
if ( is_odd_int (x))
{
odds. push_back (x);
}
}
std::copy_if
사용 Ints odds;
std::copy_if (std::begin(values), std::end(values),
std::back_inserter(odds), is_odd_int);
FunctionalPlus
의 keep_if
사용 auto odds = fplus::keep_if(is_odd_int, values);
버전 3이 작업하기에 가장 즐거울 수 있다고 생각한다면 FunctionalPlus를 좋아할 것입니다. 그리고 여전히 손으로 작성한 for 루프가 이해하기 쉽다고 생각한다면 루프 본문(예: fplus::keep_if
호출의 해당 람다 함수)이 훨씬 더 길어지면 어떻게 될지 생각해 보세요. keep_if
읽을 때 odds
values
에서 나온 요소만 포함할 수 있고 일부, 아마도 복잡한 조건자에 의해 선택될 수 있다는 것을 즉시 알 수 있습니다. for 루프의 경우 전체 루프 본문을 읽을 때까지 무슨 일이 일어나고 있는지 알 수 없습니다. 루프 버전에서는 아마도 keep_if
를 사용하면 언뜻 보기에 무엇을 알 수 있는지 설명하는 주석이 맨 위에 필요할 것입니다.
다음은 FunctionalPlus를 사용하여 함수와 컨테이너로 수행할 수 있는 유용한 작업을 보여주는 몇 가지 간단한 예입니다.
다양한 속성에 대해 컨테이너의 콘텐츠를 테스트할 수 있습니다.
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::list things = { " same old " , " same old " };
if ( fplus::all_the_same (things))
std::cout << " All things being equal. " << std::endl;
}
team
의 나컨테이너 속성을 검색하기 위한 몇 가지 편의 함수도 있습니다. 예를 들어 문자열에서 문자의 발생 횟수를 셀 수 있습니다.
# include < fplus/fplus.hpp >
# include < iostream >
int main ()
{
std::string team = " Our team is great. I love everybody I work with. " ;
std::cout << " There actually are this many 'I's in team: " <<
fplus::count ( " I " , fplus::split_words ( false , team)) << std::endl;
}
산출:
There actually are this many 'I's in team: 2
컨테이너에서 가장 높은 등급의 요소를 찾는 것은 손으로 작성한 버전에 비해 매우 간단합니다(1, 2).
# include < fplus/fplus.hpp >
# include < iostream >
struct cat
{
double cuteness () const
{
return softness_ * temperature_ * roundness_ * fur_amount_ - size_;
}
std::string name_;
double softness_;
double temperature_;
double size_;
double roundness_;
double fur_amount_;
};
void main ()
{
std::vector cats = {
{ " Tigger " , 5 , 5 , 5 , 5 , 5 },
{ " Simba " , 2 , 9 , 9 , 2 , 7 },
{ " Muffin " , 9 , 4 , 2 , 8 , 6 },
{ " Garfield " , 6 , 5 , 7 , 9 , 5 }};
auto cutest_cat = fplus::maximum_on ( std::mem_fn (&cat::cuteness), cats);
std::cout << cutest_cat. name_ <<
" is happy and sleepy. *purr* *purr* *purr* " << std::endl;
}
산출:
Muffin is happy and sleepy. *purr* *purr* *purr*
다음과 같은 함수가 주어졌다고 가정해 보겠습니다.
std::list< int > collatz_seq ( int x);
그리고 30 미만의 모든 숫자에 대한 Collatz 시퀀스의 문자열 표현을 포함하는 std::map
을 생성하려고 합니다. 이를 기능적인 방식으로도 훌륭하게 구현할 수 있습니다.
# include < fplus/fplus.hpp >
# include < iostream >
// std::list collatz_seq(std::uint64_t x) { ... }
int main ()
{
typedef std::list< int > Ints;
// [1, 2, 3 ... 29]
auto xs = fplus::numbers( 1 , 30 );
// A function that does [1, 2, 3, 4, 5] -> "[1 => 2 => 3 => 4 => 5]"
auto show_ints = fplus::bind_1st_of_2 (fplus::show_cont_with, " => " );
// A composed function that calculates a Collatz sequence and shows it.
auto show_collats_seq = fplus::compose (collatz_seq, show_ints);
// Associate the numbers with the string representation of their sequences.
auto collatz_dict = fplus::create_map_with (show_collats_seq, xs);
// Print some of the sequences.
std::cout << collatz_dict[ 13 ] << std::endl;
std::cout << collatz_dict[ 17 ] << std::endl;
}
산출:
[13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
[17 => 52 => 26 => 13 => 40 => 20 => 10 => 5 => 16 => 8 => 4 => 2 => 1]
표시된 함수는 std::vector
, std::list
, std::deque
, std::string
등과 같은 기본 STL 컨테이너뿐만 아니라 유사한 인터페이스를 제공하는 사용자 정의 컨테이너에서도 작동합니다.
FunctionalPlus는 가능한 경우 유형을 추론합니다. Collatz 예제에서 한 줄의 코드를 살펴보겠습니다.
auto show_collats_seq = fplus::compose(collatz_seq, show_ints);
collatz_seq
는 uint64_t
를 취하고 list
를 반환하는 함수입니다. show_ints
list
가져와 string
반환합니다. kennyim이 작성한 function_traits
를 사용하면 fplus::compose(collatz_seq, show_ints)
표현식을 uint64_t
를 취하고 string
을 반환하는 함수로 자동으로 추론할 수 있으므로 수동으로 유형 힌트를 제공할 필요가 없습니다. 컴파일러.
"연결 유형"이 일치하지 않는 두 함수가 전달되면 문제를 설명하는 명확한 오류 메시지가 생성됩니다. FunctionalPlus는 함수 템플릿에서 유형 오류가 발생할 때 컴파일러가 생성하는 혼란스러울 정도로 긴 오류 메시지를 방지하기 위해 컴파일 시간 어설션을 사용합니다.
프로그래밍 방식을 "자신만의 루프 및 중첩된 ifs 작성"에서 "작은 함수 구성 및 사용"으로 변경하면 컴파일 타임에는 더 많은 오류가 발생하지만 런타임에는 오류가 줄어들어 보상을 받습니다. 또한 컴파일 시간 오류가 더 정확할수록 디버깅에 소요되는 시간이 줄어듭니다.
"FunctionalPlus 라이브러리를 사용한 C++의 함수형 프로그래밍, 오늘: HackerRank 도전 보석" 기사에서는 FunctionalPlus 접근 방식을 사용하여 문제에 대한 우아한 솔루션을 개발할 수 있는 방법을 보여줌으로써 라이브러리에 대한 원활한 소개를 제공합니다.
또한 Udemy에는 일반적인 기능적 개념을 설명하기 위해 FunctionalPlus를 많이 사용하는 "C++를 사용한 기능적 프로그래밍" 과정이 있습니다.
위의 "보석" 튜토리얼에서는 기능적 사고를 적용하여 다음 문제에 대한 아래 솔루션에 도달하는 방법을 설명합니다.
입력 텍스트의 모든 줄에 있는 문자 수를 찾습니다.
std::string gemstone_count ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
const auto lines = split_lines ( false , input); // false = no empty lines
const auto sets = transform (
convert_container,
lines);
// Build the intersection of all given character sets (one per line).
const auto gem_elements = fold_left_1 (
set_intersection, sets);
return show ( size_of_cont (gem_elements));
}
namespace fwd
의 기능을 사용하면 임시 변수 없이 작업할 수 있으며 Unix 명령줄의 파이프 개념과 유사하게 전체 프로세스가 단순히 함수 체인을 통해 입력을 푸시한다는 점을 분명히 할 수 있습니다.
std::string gemstone_count_fwd_apply ( const std::string& input)
{
using namespace fplus ;
typedef std::set characters;
return fwd::apply (
input
, fwd::split_lines ( false )
, fwd::transform (convert_container)
, fwd::fold_left_1 (set_intersection)
, fwd::size_of_cont ()
, fwd::show ()
);
}
fplus::fwd::
에서는 많은 fplus::
함수를 다시 찾을 수 있지만 부분적으로 커리된 버전에서는 fplus::foo : (a, b, c) -> d
에는 fplus::foo : (a, b) -> (c -> d)
. 이렇게 하면 위의 스타일이 가능해집니다.
포워드 애플리케이션 버전 대신 포인트 없이 작성하고 구성별로 함수를 정의할 수도 있습니다.
using namespace fplus ;
typedef std::set characters;
const auto gemstone_count_fwd_compose = fwd::compose(
fwd::split_lines ( false ),
fwd::transform(convert_container),
fwd::fold_left_1(set_intersection),
fwd::size_of_cont(),
fwd::show()
);
그런데 바이너리 함수의 매개변수가 역순으로 필요한 경우에는 namespace fplus::fwd::flip
도 존재합니다. fplus::bar : (a, b) -> c
fplus::fwd::bar : a -> b -> c
뿐만 아니라 fplus::fwd::flip::bar : b -> a -> c
에도 해당 아날로그를 포함합니다. fplus::fwd::flip::bar : b -> a -> c
.
아직 이름을 모르는 특정 FunctionalPlus 함수를 찾고 있다면 물론 IDE의 자동 완성 기능을 사용하여 namespace fplus
의 콘텐츠를 찾아볼 수 있습니다. 그러나 권장되는 방법은 FunctionalPlus API 검색 웹사이트를 사용하는 것입니다. 키워드나 기능 유형 시그니처로 빠르게 검색할 수 있습니다. 원하는 경우 Sourcegraph를 사용하여 소스 코드를 찾아볼 수도 있습니다.
C++의 오버헤드 없는 추상화 개념 덕분에 기본 기능이 빠릅니다. 다음은 표준 데스크톱 PC에서 GCC 및 O3
플래그로 컴파일한 첫 번째 예의 일부 측정값입니다.
5000 random numbers, keep odd ones, 20000 consecutive runs accumulated
----------------------------------------------------------------------
| Hand-written for loop | std::copy_if | fplus::keep_if |
|-----------------------|--------------|----------------|
| 0.632 s | 0.641 s | 0.627 s |
따라서 컴파일러는 기본적으로 기계 코드 성능 측면에서 동일하도록 모든 것을 최적화하고 인라인하는 작업을 매우 잘 수행하는 것 같습니다.
더 복잡한 함수는 때로는 더 최적화된 방식으로 작성될 수 있습니다. 성능이 중요한 시나리오에서 FunctionalPlus를 사용하고 프로파일링 결과 더 빠른 버전의 함수가 필요하다는 사실이 밝혀지면 저에게 알려주시거나 FunctionalPlus 개선에 도움을 주세요.
FunctionalPlus는 주어진 컨테이너가 r-값(예: 체인 호출)인 경우 내부적으로 내부에서 작동할 수 있으므로 불필요한 많은 할당 및 복사를 피할 수 있습니다. 그러나 모든 상황에서 그런 것은 아닙니다. 그러나 다중 패러다임 언어를 사용하면 수동으로 최적화된 명령형 코드를 fplus
함수와 쉽게 결합할 수 있습니다. 다행스럽게도 경험(일명 프로파일링)에 따르면 대부분의 경우 애플리케이션에 있는 대부분의 코드는 전체 성능 및 메모리 소비와 관련이 없습니다. 따라서 처음에는 개발자 생산성과 코드 가독성에 초점을 맞추는 것이 좋습니다.
FunctionalPlus와 range-v3(C++-20의 ranges
기반)에는 다음 코드 조각에서 볼 수 있듯이 공통점이 있습니다.
const auto times_3 = []( int i){ return 3 * i;};
const auto is_odd_int = []( int i){ return i % 2 != 0 ;};
const auto as_string_length = []( int i){ return std::to_string (i). size ();};
// FunctionalPlus
using namespace fplus ;
const auto result_fplus = fwd::apply(
numbers ( 0 , 15000000 )
, fwd::transform(times_3)
, fwd::drop_if(is_odd_int)
, fwd::transform(as_string_length)
, fwd::sum());
// range-v3
const auto result_range_v3 =
accumulate (
views::ints ( 0 , ranges::unreachable)
| views::take( 15000000 )
| views::transform(times_3)
| views::remove_if(is_odd_int)
| views::transform(as_string_length), 0);
하지만 몇 가지 차이점이 있습니다. Range-v3 범위는 게으르다. 즉, 위와 같은 처리 체인의 단일 단계 중에 중간 메모리가 할당되지 않음을 의미합니다. 반면에 FunctionalPlus를 사용하면 일반 STL 컨테이너로 작업하게 됩니다. 또한 새로운 범위 어댑터를 작성하는 것보다 새로운 기능을 구현하는 것이 더 간단합니다. 또한 FunctionalPlus는 즉시 사용 가능한 훨씬 더 많은 기능을 제공하며 API 검색 웹사이트를 갖추고 있습니다. 따라서 두 라이브러리 사이의 선택은 귀하의 선호도와 프로젝트 요구 사항에 따라 달라집니다.
C++14 호환 컴파일러가 필요합니다. 다음 버전의 컴파일러는 괜찮습니다.
FunctionalPlus를 설치하는 다양한 방법에 대한 가이드는 INSTALL.md에서 찾을 수 있습니다.
이 라이브러리의 기능은 처음에는 C++를 정기적으로 사용하는 동안 개인적인 필요로 인해 증가했습니다. 최대한 오류 없이 편안하게 사용할 수 있도록 최선을 다하겠습니다. API는 앞으로도 계속 변경될 수 있습니다. 제안 사항이 있거나, 오류를 발견하거나, 일부 기능이 누락되었거나, 일반적인 피드백/비판을 제공하려는 경우 언제든지 연락 주시기 바랍니다. 물론 기여도 매우 환영합니다.
Boost 소프트웨어 라이선스 버전 1.0에 따라 배포됩니다. (동봉된 파일 LICENSE
참조하거나 http://www.boost.org/LICENSE_1_0.txt에서 복사하세요)