カリー化の概念と使用方法は知っていますが、実際にどのような価値があるのでしょうか。
3 に答える
関連する質問がカバーするように、カレー関数の実用的な使用?、人々がカリー化を大切にし、それを利用する理由はたくさんあります。
- コードの再利用の改善-特殊なケースの関数は、単に部分的に適用された(そしてカレーされた)ジェネリック関数です
- コードの読みやすさの向上-
map (+2)
より読みやすいmap (\x -> x + 2)
- パフォーマンスの向上-カリー化は特定の特殊化を明らかにすることができ、優れたコンパイラーはあなたのために特殊化されたバージョンを生成します
- 楽しい-よりシンプルなコード、より美しいコードは人生をより楽しくします。
私が見つけた本当の利点:
バグが少ない- 関数合成によってコードを合成すると、命令型の制御フローよりも正確なコードになる傾向があります。たとえば、「for ループ」ではなく「map」を使用すると、多くの「off by one」インデックス作成エラーのリスクを排除できます。
並行性の向上- 純粋で副作用のない関数で作成したコードは、自動的にスレッド セーフになります。これを不変の永続的なデータ構造と組み合わせると、堅牢な並行コードを作成するための優れたレシピが得られます。Clojure はこれに特に適しています。http://www.infoq.com/presentations/Value-Identity-State-Rich-Hickeyを参照してください。
より簡潔で管理しやすいコード- 私の完全に非科学的な分析では、機能的なコードは命令型の OOP コードよりもかなり短いようです。この利点は、プログラムが大きくなるにつれて顕著になる傾向があります。これにはいくつかの理由があると思います:
- 関数型言語の構文はかなり簡潔になる傾向があります。たとえば、Haskell とさまざまな Lisp はどちらも、非常に単純で洗練された構文を持っています。
- 関数型言語は、プログラムの一部を「接着」するのではなく、(高次関数の) 構成を促進する傾向があります。したがって、他の多くのパラダイムに固有のボイラープレートの多くを回避します。
- 構成点に関連して、DRY 原則を関数型言語に適用する方が簡単であることがわかりました。共通のパターンが見られる場合は、ほとんどの場合、これを高階関数 (または Lisper の場合はマクロ) に簡単に抽出して、他の場所で使用できるようにすることができます。
テスト容易性 - 主に純粋な関数を使用してコードを作成すると、堅牢なテストを簡単に作成できます。
もちろん、これを相殺するためのいくつかの欠点があります。
- 書くのは難しいです。かなり複雑な抽象的な概念を頭の中に保持する必要があるため、精神的な敏捷性が必要です。あなたが訓練された数学者であれば役に立ちますが (私はそうです)、関数型のコードを書くことは OOP より難しいと思います。
- パフォーマンスのオーバーヘッドがいくらかあります。関数型言語は、必然的にある程度のオーバーヘッドを意味するさまざまな構造を使用します。これは優れたコンパイラでかなり小さくすることができますが、完全に排除することはできません。
- ライブラリ/ツールのサポート - これはほぼ完全に OOP プラットフォームとツールの成熟度の向上によるものですが、それでもなお問題です。長期的にはこれは問題にはなりませんが、私が見つけた最善の解決策は、Clojure を使用することです。Clojure は、ほとんどの Java プラットフォーム ライブラリとツールから恩恵を受けることができます。
ワンス・アンド・オンリー・ワンスに少し似ていると思います
C/C++ では、次のようなコードを書いています。
configure_grid (grid, first_column, last_column, action) {
for (i = first_column; i <= last_column; ++i)
// ...
}
configure_grids (action) {
congifure_grid (alpha, first_alpha, last_alpha, action);
congifure_grid (beta, first_beta, last_beta, action);
}
アルファとベータのそれぞれに対して for ループを 1 回記述する代わりに。これは、手続き型コードのカリー化に似ています。ここでの利点は明らかです。
カリー化は重要な理論的概念ですが、実際には、これが利点です。
実際、C でテスト スイートを書いたことがあるのを覚えています。
typedef bool (*predicate) (const type *);
const char * argument;
bool do_foo (const type * t) {
return bar (t, argument);
}
bool do_baz (const type * t) {
return bap (t, argument);
}
predicate foo (const char * arg) {
argument = arg;
return do_foo;
}
predicate baz (const char * arg) {
argument = arg;
return do_baz;
}
assert (for_all (data_set("alpha"), foo ("abc")));
assert (for_all (data_set("beta"), baz ("def")));
これはすべて純粋な C であり、マクロのトリックなどはありません。関数型スタイルで、カリー化のようなものです。ここでの利点は、テスト ケースが何であるかを一目で正確に確認できることです。data_set
似ています -- データを取得する別の関数に引数をバインドします:for_all
サンクを実行し、述語をチェックし、クリーンアップします。几帳面。