C#関数式プログラミングの関数構築関数

9388 ワード

オブジェクト向けのプログラミングでは、他のクラスを多重化する必要がある場合は、継承によって実現できます.関数式プログラミングでは、これらの関数を多重化する方法も異なります.今日のチュートリアルでは、2つの方法について説明します.1つは、複数の関数を組み合わせて1つの関数にすることです.もう1つは、前に紹介したアプリケーションの一部です.もちろん、使用要件に合致するように高度化する方法について説明します.
 
コンポジット
文字通り、組み合わせは関数Aの結果を関数Bに渡すことである.しかし、私たちは関数Aの結果に注目していません.もちろん、多くの人は必ずそうします.
1 var r1 = funcA(1);
2 var r2 = funcB(r1);

 
 
これは明らかに私たちが望んでいるほどではありません.私たちが後でこのような関数をよく利用する必要があると仮定します.問題が発生したので、組み合わせを利用して新しい関数を合成する必要があります.まず、組み合わせに使用する2つの関数を書きます.
1 public static int FuncA(int x)
2 {
3      return x + 3;
4 }
5 
6 public static int FuncB(int x)
7 {
8     return x + 6;
9 }

自動化関数を使用しない場合は、次のような書き方で組み合わせることができます.
Func<int,int> funcC = x => FuncB(FuncA(x));

 
 
しかし、ここではvarは使用できません.C#の自動推定タイプはこのタイプを推定できないからです.これにより新しい関数funcCが得られ,この関数を実行して最終的な結果を見ることができる.手動で組み合わせを行い、自動化された関数を作成してこの操作を行います.
1 public static Func<T1, T3> Compose<T1, T2, T3>(Func<T1, T2> func1, Func<T2, T3> func2)
2 {
3     return x => func2(func1(x));
4 }

次に、この関数を使用して、上記の機能を実現します.
var funcC = Compose<int, int, int>(FuncA, FuncB);

 
 
しかし,タイプ推定に依存することなく汎用パラメータを提供する必要があることが分かった.しかし、FuncAとFuncBがこれまで明示的に宣言されている場合は、汎用パラメータを提供する必要はありません.たとえば、FuncAとFuncBを次のように書く必要はありません.
Func<int, int> FuncA = x => x + 2;
Func<int, int> FuncB = x => x + 6;

 
 
      これでCompose関数を呼び出すには汎用パラメータを提供する必要はありません.ちなみにここでは他の言語で同じ機能を実現する方法を紹介します.F#ではFuncB>>FuncAで実現しますが、HaskellではFuncA.FuncBで実現しています.C#に比べて実現は非常に簡単です.上記の例では,関数Aの戻りタイプが関数Bのパラメータタイプと一致しなければならず,この関数チェーンでは最初の関数だけが複数のパラメータを持つことができ,他の関数は1つの関数しか持たないという問題も発見した.もちろん、関数チェーンの最後の関数はActionです.つまり、戻り値がなくてもいいです.次に、3つの関数を組み合わせることができる自動化関数を書きます.
1 public static Func<T1, T4> Compose<T1, T2, T3, T4>(Func<T1, T2> func1, Func<T2, T3> func2, Func<T3, T4> func3)
2 {
3      return x => func3(func2(func1(x)));
4 }

 
もちろん実際の開発では書く必要はありませんが、FCSlibで提供されている関数を直接利用することができます.
 
高度な部分応用
      「関数式プログラミングの部分応用」を学んだことがある人は、いくつかの応用は複数のパラメータが必要な関数を1つの関数チェーンに分解し、各関数チェーンには1つのパラメータしか必要としないことを知っているに違いない.そこで以下の内容は筆者が繰り返し紹介した内容ではなく,読者が学習していない場合は,上記の対応するページにアクセスして学習することができる.
 
C#にこの自動化関数のパラメータを一部適用する方法があり、タイプ推定が機能しない場合は、次のような煩雑なタイプパラメータを入力する必要があります.
Functional.Curry<Converter<int,int>,Ienumerable<int>,Ienumerable<int>>(Functional.Map<int,int>);

読者はタイプパラメータが半分を占めていることを発見し、この問題を解決する方法も紹介したので、タイプを明示的に宣言した関数を書いてMap関数をカプセル化することができます.
public static Func<Converter<int, int>, IEnumerable<int>, IEnumerable<int>> MapDelegate<T1, T2>()
{
      return Map<T1, T2>;
}

 
これにより、Curry関数を呼び出すときにタイプパラメータを指定する必要がなくなります.
Functional.Curry(Functional.MapDelegate<int,int>());

 
 
      これで,タイプ推定の問題は解決した.実際の開発では,一部の応用は非常に有用であるが,関数Filterのように2つのアルゴリズムが必要であり,最後のパラメータがデータである場合がある.実際の使用では2つのアルゴリズムを割り当てますが、後の使用では対応するデータを変更するだけですが、一部の適用を採用すると面倒になります.以下はFilter関数の実装です.
 1         public static IEnumerable<R> Filter<T,R>(Func<T,R> map,Func<T, bool> compare, IEnumerable<T> datas)
 2         {
 3             foreach (T item in datas)
 4             {
 5                 if (compare(item))
 6                 {
 7                     yield return map(item);
 8                 }
 9             }
10         }

具体的な機能はcompare関数で条件に合致するか否かを判断しmap関数で必要な部分を返すことである.この関数を呼び出すには、次のようにします.
1             foreach (int x in Filter<int, int>(x => x, x => x <= 10, new int[] { 2, 3, 1, 4, 5, 3, 34 }))
2             {
3                 Console.WriteLine(x);
4             }
5             Console.ReadKey();

 
一部のアプリケーションを使用する前に、この関数のDelegateバージョンを書き出します.これにより、タイプ推定を使用できます.
1         public static Func<Func<T, R>, Func<T, bool>, IEnumerable<T>, IEnumerable<R>> FilterDelegate<T, R>()
2         {
3             return Filter<T, R>;
4         }

 
それから私たちは簡単にCurrey関数を使ってその部分を応用することができて、ここで筆者は直接自分で1つのCurrey関数を実現して、FCSlibの中で提供したものを使っていません.読者は以下を参考にすることができます.
1         public static Func<T1,Func<T2,Func<T3,R>>> Currey<T1,T2,T3,R>(Func<T1,T2,T3,R> func)
2         {
3             return x => y => z => func(x, y, z);
4         }

最後に実際に使ってみましょう
1             var f = Currey(FilterDelegate<int, int>());
2             foreach (int x in f(x => x)(x => x <= 10)(new int[] { 2, 3, 1, 4, 5, 3, 34 }))
3             {
4                 Console.WriteLine(x);
5             }
6             Console.ReadKey();

 
 
それでも煩雑なので、より高度なアプリケーションの一部が必要です.ここでは、実装を支援する別の自動化関数が必要です.
1         public static Func<T3,R> Apply<T1, T2, T3, R>(Func<T1, Func<T2, Func<T3, R>>> func,T1 arg1,T2 arg2)
2         {
3             return x => func(arg1)(arg2)(x);
4         }

この関数の役割は,元の部分に適用された関数を2つのパラメータを受信し,1つのパラメータのみを受信する関数に戻すことである.アルゴリズム部分は変動しないが,データは常に変動する.次に、実際の運用を示します.
 1             var f = Apply(Currey(FilterDelegate<int, int>()), x => x, x => x <= 10);
 2 
 3             foreach (int x in f(new int[] { 2, 3, 1, 4, 5, 3, 34 }))
 4             {
 5                 Console.WriteLine(x);
 6             }
 7             foreach (int x in f(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }))
 8             {
 9                 Console.WriteLine(x);
10             }
11             Console.ReadKey();

 
 
このように振り回された後、私たちは本当に必要な関数を得ました.私たちは最初にアルゴリズムを決定しました.その後の使用では、データだけを転送することができます.