私は、言語とその性質、私のドメイン、さらには言語の使用方法によっても、C ++でそのような概念を適用するものとして偏見を抱いています。しかし、これらのことを考えると、スレッドセーフティ、システムに関する推論の容易さ、関数の再利用の発見など、関数型プログラミングに関連する利点の大部分を享受することになると、不変のデザインは最も面白くない側面だと思います不快な驚きなしにそれらを任意の順序で結合します)など。
この単純なC ++の例を見てください(明らかに、単純化のために最適化されていないため、画像処理の専門家の前で恥ずかしくなることはありません)。
// Inputs an image and outputs a new one with the specified size.
Image resized_image(const Image& src, int new_w, int new_h)
{
Image dst(new_w, new_h);
for (int y=0; y < new_h; ++y)
{
for (int x=0; x < new_w; ++x)
dst[y][x] = src.sample(x / (float)new_w, y / (float)new_h);
}
return dst;
}
この関数の実装は、2つのカウンター変数と一時的なローカルイメージの形式でローカル(および一時)状態を変化させて出力しますが、外部からの副作用はありません。画像を入力し、新しい画像を出力します。それを心のコンテンツにマルチスレッド化できます。推論するのは簡単で、徹底的にテストするのは簡単です。何かがスローされた場合、新しいイメージは自動的に破棄され、外部の副作用をロールバックすることを心配する必要がないため、例外に対して安全です(関数のスコープ外で変更される外部イメージはありません)。
Image
上記のコンテキストを不変にすることで、上記の関数を実装するのが面倒になり、おそらく少し効率が低下する場合を除いて、上記のコンテキストで不変にすることで、ほとんど得られず、潜在的に多くが失われます。
純度
したがって、純粋な関数(外部副作用がない)は非常に興味深いものであり、C ++でもチームメンバーに頻繁に使用することの重要性を強調しています。しかし、一般的にコンテキストとニュアンスがないだけで適用される不変のデザインは、言語の命令的な性質を考えると、あまり効率的ではないため、ローカルの一時オブジェクトを効率的に変更できることがしばしば便利で実用的です(両方とも開発者およびハードウェア向け)純粋な機能の実装。
多額の構造の安価なコピー
私が見つけた2番目に有用なプロパティは、厳密な入力/出力の性質を考慮して関数を純粋にするためにしばしば発生するように、そのコストが非常に重い場合に、非常に重いデータ構造を安価にコピーする能力です。これらは、スタックに収まる小さな構造ではありません。Scene
ビデオゲームの全体のように、大きくて重たい構造になるでしょう。
その場合、レンダラーが同時に描画しようとしているシーンを物理が変化させていると同時に物理を深くしていると、物理と並列をロックおよびボトルネックせずに物理とレンダリングを効率的に並列化するのが難しいため、コピーのオーバーヘッドが効果的な並列処理の機会を妨げる可能性があります物理学を適用した1つのフレームを出力するためだけにゲームシーン全体をコピーすることも、同様に効果がない場合があります。ただし、物理システムが単にシーンを入力し、物理を適用した新しいシーンを出力するという意味で物理システムが「純粋」であり、そのような純度が天文学的なコピーのオーバーヘッドを犠牲にしていなければ、安全に並行して動作できます一方を待たないレンダラー。
したがって、アプリケーションの状態の非常に重いデータを安価にコピーし、処理とメモリ使用に最小限のコストで新しい修正バージョンを出力する機能は、純粋性と効果的な並列性の新しい扉を開くことができます。永続的なデータ構造の実装方法から。しかし、このようなレッスンを使用して作成するものはすべて、完全に永続的である必要はなく、不変のインターフェース(たとえば、コピーオンライト、または「ビルダー/一時」を使用する)を提供する必要はありません。関数/システム/パイプラインの並列性と純度を追求する際に、メモリ使用量とメモリアクセスを2倍にすることなく、コピーの一部だけをコピーして変更します。
不変性
最後に、これら3つの中で最も面白くないと考える不変性がありますが、特定のオブジェクト設計が純粋な機能のローカル一時として使用されることを意図していない場合、鉄の拳で強制することができます。すべてのメソッドで外部副作用を引き起こさない(オブジェクトのメソッドの直接のローカルスコープ外のメンバー変数を変更しない)ため、一種の「オブジェクトレベルの純度」。
そして、C ++のような言語では、これら3つのうちで最も面白くないと思いますが、確かに単純なオブジェクトのテストとスレッドセーフおよび推論を単純化できます。たとえば、オブジェクトにそのコンストラクターの外部で一意の状態の組み合わせを与えることはできず、参照やポインターによってもconstnessやread-元のコンテンツが変更されないことを保証しながら(イテレータとハンドルなどのみ)(少なくとも、言語内でできる限り)。
しかし、私はこれを最も面白くないプロパティだと思います。なぜなら、ほとんどのオブジェクトは一時的に、可変形式で使用されて純粋な機能(またはオブジェクトまたは一連の「純粋なシステム」単に何かを入力し、他の何かに触れることなく新しいものを出力するという究極の効果で機能します)、主に命令型言語での四肢への不変性は、かなり逆効果的な目標だと思います。コードベースの中で最も役立つ部分には、控えめに適用します。
最後に:
[...]永続的なデータ構造だけでは、あるスレッドが他のスレッドに見える変更を加えるシナリオを処理するのに十分ではないように思われます。このためには、アトム、参照、ソフトウェアトランザクションメモリ、またはクラシックロックや同期メカニズムなどのデバイスを使用する必要があるようです。
当然、デザインが(ユーザーエンドデザインの意味で)複数のスレッドが同時に発生するように見えるように変更する必要がある場合、同期または少なくとも描画ボードに戻って、これに対処するための洗練された方法(関数型プログラミングのこの種の問題を扱う専門家が使用する非常に精巧な例をいくつか見ました。
しかし、永続的なデータ構造の例のように、そのようなコピーと、巨大な構造の部分的に変更されたバージョンを安価に出力できるようになると、多くのドアと機会が開かれることがよくあります厳密なI / O並べ替えの並列パイプラインで互いに完全に独立して実行できるコードを並列化することについて、これまで考えたことはありませんでした。アルゴリズムの一部が本質的にシリアルである必要がある場合でも、その処理を単一のスレッドに延期するかもしれませんが、これらの概念に頼ることで簡単に、そして心配なく、多額の作業の90%を並列化することができます