簡潔で読みやすい C++ コードを作成するのに役立ちます。
優れたコードはほとんどが自己文書化されているはずですが、実際に C++ を使用していると、コードの実際の本質から目を逸らしてしまうイテレータや手書きのループなどの低レベルのものを扱っていることに気づくことがあります。
FunctionalPlus は、コード ノイズを削減し、一度に 1 つの抽象レベルのみを処理することをサポートする、ヘッダーのみの小さなライブラリです。コードの簡潔さと保守性を高めることで、長期的には生産性 (そして楽しみ!) が向上します。一般的に使用される制御フローを何度も実装する必要がない、純粋で使いやすい機能を提供することで、これらの目標を追求します。
数値のリストがあり、奇数の数値のみに興味があるとします。
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 の例から 1 行のコードを取り出してみましょう。
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
返す関数であると自動的に推定できるため、型ヒントを手動で提供する必要はありません。コンパイラ。
「接続タイプ」が一致しない 2 つの関数が渡された場合、問題を説明する明確なエラー メッセージが生成されます。 FunctionalPlus はコンパイル時のアサーションを使用して、関数テンプレートで型エラーが発生したときにコンパイラーが生成する混乱を招くほど長いエラー メッセージを回避します。
プログラム方法を「独自のループとネストされた if を作成する」から「小さな関数を作成して使用する」に変更すると、コンパイル時のエラーが増加しますが、実行時のエラーが少なくなるという効果が得られます。また、コンパイル時エラーがより正確になると、デバッグにかかる時間が短縮されます。
記事「FunctionalPlus ライブラリを使用した C++ の関数プログラミング、今日: HackerRank チャレンジ Gemstones」では、FunctionalPlus アプローチを使用して問題に対する洗練されたソリューションを開発する方法を示し、ライブラリへのスムーズな入門を提供します。
また、Udemy には、FunctionalPlus を多用して一般的な関数の概念を説明する「C++ を使用した関数型プログラミング」コースがあります。
上記の「Gemstones」チュートリアルでは、関数的思考を適用して次の問題の解決策に到達する方法を説明しています。
入力テキストの各行に存在する文字数を見つけます。
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 検索 Web サイトを使用することです。キーワードや関数タイプのシグネチャですばやく検索できます。必要に応じて、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 は内部的に、特定のコンテナーが右側の値 (連鎖呼び出しなど) である場合にインプレースで動作できることが多いため、多くの不必要な割り当てやコピーが回避されます。しかし、これはすべての状況に当てはまるわけではありません。ただし、マルチパラダイム言語を使用することで、手動で最適化された命令型コードと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 検索 Web サイトを備えています。したがって、2 つのライブラリのどちらを選択するかは、好みとプロジェクトのニーズによって異なります。
C++14互換のコンパイラが必要です。これらのバージョン以降のコンパイラは問題ありません。
FunctionalPlus をインストールするさまざまな方法のガイドは、INSTALL.md にあります。
このライブラリの機能は当初、C++ を定期的に使用する中での個人的なニーズにより拡張されました。エラーがなく、できるだけ快適に使用できるよう最善を尽くしています。 API は将来的に変更される可能性があります。ご提案がある場合、エラーを見つけた場合、一部の機能が不足している場合、または一般的なフィードバック/批評をご希望の場合は、ぜひご連絡ください。もちろん、投稿も大歓迎です。
Boost Software License バージョン 1.0 に基づいて配布されます。 (添付ファイルLICENSE
を参照するか、http://www.boost.org/LICENSE_1_0.txt でコピーしてください)