9

再利用可能なライブラリに関する FP 設計の重要な違いの前提の 1 つとして (私が学んでいることについて)、これらは対応する OO (一般に) よりもデータ中心であるということです。

これは、 TFD (Type-First-Development)などの新しい技術からも確認されているようで、Tomas Petricek がこのブログ投稿で詳しく説明しています。

現在、言語はマルチパラダイムであり、同じ Petricek がその本で C# から使用できるさまざまな関数型テクニックを説明しています。

ここで私が興味を持っているのは、コードを適切に分割する方法です

そのため、判別共用体 (Petricek book に示されている) に相当するものを使用してライブラリ データ構造を定義し、私の要件のドメイン ロジックに従って、それらを不変リストおよび/またはタプルと共に使用することを計画しています。

データ構造に作用する操作 (メソッド ... 関数) はどこに配置すればよいですか?

標準の delegates で具体化された関数値を使用する高階関数を定義したい場合Func<T1...TResult>、どこに配置すればよいですか?

常識的には、これらのメソッドを静的クラスにグループ化するように言われていますが、C# で機能ライブラリを既に作成している人々からの確認が必要です。

これが正しく、次のような高次関数があると仮定します。

static class AnimalTopology {
  IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector) {
    // remainder omitted
  }
}

選択した脊椎動物に、ライブラリで公開したい N 個の特定のケースがある場合、それらを公開するより正しい方法はどれですか。

static class VertebratedSelectorsA {
  // this is compatible with "Func<Skeleton, bool> selector"
  static bool Algorithm1(Skeleton s) {
    //...
  } 
}

また

static class VertebratedSelectorsB {
  // this method creates the function for later application
  static Func<Skeleton, bool> CreateAlgorithm1Selector(Skeleton s) {
     // ...
  } 
}

どんな指摘も非常に高く評価されます。

編集:

Mads Torgersen による T. Petricek のReal World Functional Programmingの序文から 2 つのフレーズを引用したいと思います。

[...] C# で関数型プログラミング手法を使用すると、大きなメリットが得られますが、F# で行う方が簡単で自然です。[...] 関数型プログラミングは心の状態です。[...]

編集-2:

  • 質問をさらに明確にする必要があると感じています。タイトルで言及されている関数は、関数型プログラミングに厳密に関連してます。私は、より論理的な方法や一般的により理にかなった方法という意味で、メソッドをグループ化するより機能的な方法を求めているわけではありません。

  • これは、実装がNOOOマニフェストによって要約され、便宜上および明確にするためにここに引用されているFP の基本概念に可能な限り従おうとすることを意味します。

  1. クラスの関数と型
  2. 可変性よりも純度
  3. 継承より合成
  4. メソッドディスパッチに対する高階関数
  5. null に対するオプション

問題は、FP の概念に従って記述された C# ライブラリをどのようにレイアウトするかに関するものであるため、(たとえば) データ構造内にメソッドを配置するオプションは絶対にありません。これは、オブジェクト指向パラダイムの創設者だからです。

編集-3:

また、質問に対する回答 (およびさまざまなコメント) が得られたとしても、あるプログラミング パラダイムが別のパラダイムよりも優れていると言われているという誤った印象を与えたくありません。以前のように、FP の権威である Don Syme について、著書 Expert F# 3.0 (ch.20 - Designing F# Libraries - pg.565) で言及します。

[...] 関数型プログラミングとオブジェクト指向プログラミングの方法論が競合するというのはよくある誤解です。実際、それらはほぼ直交しています。[...]

4

2 に答える 2

3

注:より短く、より的確な回答が必要な場合は、他の回答を参照してください。私は、ここでこれがとりとめのない、永遠に続き、あなたの問題を過ぎ去っているように見えるかもしれないことを認識していますが、おそらくいくつかのアイデアを与えるでしょう.

と の正確な関係を知らずに質問に答えることは困難AnimalですSkeleton。この関係については、回答の後半で推奨しますが、その前に、投稿に表示されている内容に沿って進めます.

まず、あなたのコードからいくつかのことを推測しようとします:

static class AnimalTopology
{
    // Note: I made this function `static`... or did you omit the keyword on purpose?
    static IEnumerable<Animal> ListVertebrated(Func<Skeleton, bool> selector)
    {
        …
    }
}
  1. 機能原則に従ってその機能を設計した場合、副作用はありません。つまり、その出力は引数のみに依存します。(そして、セミオブジェクト指向の設定では、おそらく の他の静的メンバーAnimalTopology; しかし、あなたは何も示さなかったので、その可能性を無視しましょう.)

  2. 関数が実際に副作用がない (そして の静的メンバーにアクセスしない) 場合、関数の型シグネチャは、からを派生できることAnimalTopologyを示唆しています。AnimalSkeletonSkeletonAnimal

  3. もしこれも本当なら、答えを出すために次のように仮定しましょう。

    class Skeleton
    {
        …
        public Animal Animal { get { … } } // Skeletons have animals!? We'll get to that.
    }
    

Animal関数はs からsを派生させることができるため、実装が不可能であることは明らかですが、Skeletonまったく受け取りませんSkeleton。に作用する述語関数のみを受け取りSkeletonます。( type の 2 番目のパラメーターを追加することでこれを修正できますFunc<IEnumerable<Skeleton>> getSkeletonsが、...)

私の意見では、次のようなものがより理にかなっています。

static IEnumerable<Animal> GetVertebrates(this IEnumerable<Skeleton> skeletons,
                                          Func<Skeleton, bool> isVertebrate)
{
    return skeletons
           .Where(isVertebrate)
           .Select(s => s.Animal);
}

さて、なぜ骨格から動物を推測しているのか疑問に思われるかもしれません。そして、bool「脊椎動物である」という特性は、動物(または骨格)の固有の特性ではありませんか?これを決定する方法は本当にいくつかありますか?

次のことをお勧めします。

class Animal
{
     Skeleton Skeleton { get; } // not only vertebrates have skeletons! 
}

class Vertebrate : Animal { … } // vertebrates are a kind of animal 

static class AnimalsExtensions
{
    static IEnumerable<Vertebrate> ThatAreVertebrates(this IEnumerable<Animal> animals)
    {
        return animals.OfType<Vertebrate>();
    }
}

上記の拡張メソッドの使用に注意してください。使用方法の例を次に示します。

List<Animal> animals = …;
IEnumerable<Vertebrate> vertebrates = animals.ThatAreVertebrates();

ここで、拡張メソッドがより複雑な作業を行ったとします。その場合、独自の指定された「アルゴリズム タイプ」内に配置することをお勧めします。

interface IVertebrateSelectionAlgorithm
{
    IEnumerable<Vertebrate> GetVertebrates(IEnumerable<Animal> animals);
}

これには、クラスコンストラクターなどを介して設定/パラメーター化できるという利点があります。また、アルゴリズムをいくつかのメソッドに分割して、すべてが同じクラスに存在するようにすることもできます (ただし、すべて.privateを除くGetVertebrates)。

もちろん、関数型クロージャーを使用して同じ種類のパラメーター化を行うことはできますが、私の経験では、C# の設定ではすぐに面倒になります。ここで、クラスは一連の関数を 1 つの論理エンティティとしてグループ化するための優れた手段です。

于 2013-03-29T10:01:27.530 に答える
2

データ構造に作用する操作 (メソッド ... 関数) はどこに配置すればよいですか?

4 つの一般的なアプローチが見られます (順不同)。

  • 関数をデータ構造内に配置します。(これはオブジェクト指向の「メソッド」アプローチです。関数がその型のインスタンスに対してのみ作用する場合に適しています。たとえば、関数が異なる型の複数のオブジェクトを「一緒に描画」し、さらに別のタイプのオブジェクト。この場合、私は...)

  • 関数を独自の指定された「アルゴリズム クラス」内に配置します。(これは、関数が多くのまたは複雑な作業を行う場合、またはパラメーター化/構成する必要がある場合、またはアルゴリズムを複数の関数に分割し、それらをクラス型に入れて論理的に「グループ化」できる場合に妥当と思われます。 )

  • 関数をラムダ(別名、匿名デリゲート、クロージャなど) に変換します。(これは、サイズが小さく、特定の 1 か所でのみ必要な場合にうまく機能します。コードを別の場所で簡単に再利用することはできません。)

  • 関数を static クラスに入れ、それらを拡張メソッドにします。(これが LINQ to Objects のしくみです。これは、ハイブリッドな機能とオブジェクト指向のアプローチです。発見可能性/名前空間の問題を正しく理解するには、特別な注意が必要です。多くの人は、このアプローチが行き過ぎた場合に「カプセル化」を破ると考えるでしょう。反論するには、優れた C++ の記事「非メンバー関数がカプセル化を改善する方法」を読んでください。「非メンバー フレンド関数」を「拡張メソッド」に置き換えてください。)

注:必要に応じて、これらのそれぞれについて詳しく説明することもできますが、その前に、この回答がどのようなフィードバックを受け取るかを待ちます.

于 2013-03-29T10:10:36.053 に答える