関数型プログラミングについて言及するとき、誰もが非常に柔軟で動的な構文を備えた LISP や Haskell などの古代の関数型言語を思い浮かべるはずです。最近では、Ruby、JavaScript、F# も関数型プログラミングで人気の言語です。ただし、.net はラムダ式をサポートしているため、C# は命令型プログラミング言語ではありますが、関数型プログラミングにおいては劣りません。 C# でコードを記述するプロセスでは、高階関数、結合関数、純粋関数キャッシュなどのアイデアを意図的または意図せずに使用します。式ツリーなどのアイデアも、関数型プログラミングのアイデアから生まれています。次に、一般的に使用される関数型プログラミングのシナリオをまとめます。これは、プログラミング プロセスにこれらのテクノロジを柔軟に適用し、設計のアイデアを拡張し、コードの品質を向上させるのに役立ちます。
平たく言えば、高階関数: 関数をパラメータとして使用する関数は、高階関数と呼ばれます。この定義によれば、LINQ 式、Where、Select、SelectMany、First および .net で広く使用されているその他のメソッドはすべて高階関数です。では、独自のコードを作成するときにこの設計をいつ使用するのでしょうか。
例: 不動産手数料を計算する関数、var Fee=square*price を設計します。面積 (平方) は、不動産の性質に応じてさまざまな方法で計算されます。民間住宅、商業住宅などにはさまざまな係数を掛ける必要があります。そのようなニーズに応じて、次の関数を設計しようとします:
住宅地:
rreee商業地域および住宅地域:
public Func<int,int,decimal> SquareForCivil() { return (width,hight)=>width*hight; }
これらの関数はすべて、Func
public Func<int, int, decimal> SquareForBusiness() { return (width, hight) => width * hight*1.2m; }
テストを書いて見てください
rreeC# は実行時に厳密な評価戦略を使用します。いわゆる厳密な評価とは、パラメーターが関数に渡される前に評価されることを意味します。この説明ではまだ少し分かりにくいでしょうか?シナリオを見てみましょう。実行する必要があるタスクがあり、そのためには現在のメモリ使用量が 80% 未満であり、前の計算の結果が 100 未満である必要があります。この条件は、タスクを実行する前に満たすことができます。実行されました。
この要件を満たす C# コードをすぐに書くことができます:
public decimal PropertyFee(decimal price,int width,int hight, Func<int, int, decimal> square) { return price*square(width, hight); }
NextStep を実行するときは、メモリ使用量と最初のステップ (関数 BigCalculationForFirstStep) の計算結果を渡す必要があります。コードに示されているように、最初のステップの操作は非常に時間がかかりますが、これは厳密な評価戦略のためです。 C# の for ステートメント If (memoryUtilization<0.8&&firstStepDistance<100)、メモリ使用量が 80% を超えている場合でも、明らかに、メモリ使用量が 80% を超えている場合は、最初のステップを実行する必要があります。 firstStepDistance は重要ではなくなり、計算する必要がなくなります。
したがって、遅延評価とは、結果が実際に必要な場合にのみ式または式の一部が評価されることを意味します。高階関数を使用してこの要件を書き直そうとします:
[Test] public void Should_calculate_propertyFee_for_two_area() { //Arrange var calculator = new PropertyFeeCalculator(); //Act var feeForBusiness= calculator.PropertyFee(2m,2, 2, calculator.SquareForBusiness()); var feeForCivil = calculator.PropertyFee(1m, 2, 2, calculator.SquareForCivil()); //Assert feeForBusiness.Should().Be(9.6m); feeForCivil.Should().Be(4m); }
コードは非常に単純で、関数式を使用して関数の値を置き換えるだけです。 if (memoryUtilization() < 0.8..) が満たされていない場合、次の関数は実行されません。Microsoft は .net4.0 バージョンで Lazy< を追加しました。 ;T> クラスでは、このようなニーズがあるシナリオでこのメカニズムを使用できます
カリー化は部分適用とも呼ばれます。定義: 複数のパラメーターを受け入れる関数を 1 つのパラメーター (元の関数の最初のパラメーター) を受け入れる関数に変換し、残りのパラメーターを受け入れて結果を返す新しい関数を返すテクノロジーです。 PS: なぜですか。公式の説明はそんなにわかりにくいですか?
このような定義を見ると、おそらく誰もがこれが何なのかを理解するのは難しいでしょう。それでは、カレーの原理から始めましょう:
2 つの数値を加算する関数を作成します:
public double MemoryUtilization() { //计算目前内存使用率 var pcInfo = new ComputerInfo(); var usedMem = pcInfo.TotalPhysicalMemory - pcInfo.AvailablePhysicalMemory; return (double)(usedMem / Convert.ToDecimal(pcInfo.TotalPhysicalMemory)); } public int BigCalculatationForFirstStep() { //第一步运算 System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2)); Console.WriteLine("big calulation"); FirstStepExecuted = true; return 10; } public void NextStep(double memoryUtilization,int firstStepDistance) { //下一步运算 if(memoryUtilization<0.8&&firstStepDistance<100) { Console.WriteLine("Next step"); } }
さて、この機能はどうやって使うのでしょうか?
りー1+2=3、呼び出しは非常に簡単です。要件をアップグレードするには、パラメーター (数値) の入力を必要とし、10 + 入力パラメーター (数値) の結果を計算する関数が必要です。最初のパラメータとして 10 を渡せば、上記のコードはこの要件を完全に達成できる、と誰かが言うと思いますが、そう思うなら、私は何もする必要はありません。別のオーバーロードを記述するにはパラメータが 1 つだけ必要だと言う人もいるかもしれませんが、実際には他の人が提供する API を呼び出しているため、オーバーロードを追加することはできません。部分アプリケーションの使用シナリオはあまり一般的なシナリオではないことがわかります。そのため、最適な設計は、適切なシーンに適切なテクノロジーを適合させることです。部分アプリケーションの実装を見てみましょう:
public void NextStepWithOrderFunction(Func<double> memoryUtilization,Func<int> firstStep) { if (memoryUtilization() < 0.8 && firstStep() < 100) { Console.WriteLine("Next step"); } }
式 x => y => int 型の関数によって取得される関数シグネチャ。この時点でもう一度電話する場合:
//Act var curringResult = curringReasoning.AddTwoNumberCurrying()(10); var result = curringResult(2); //Assert result.Should().Be(12);
这句话:var curringResult = curringReasoning.AddTwoNumberCurrying()(10); 生成的函数就是只接收一个参数(number),且可以计算出10+number的函数。
同样的道理,三个数相加的函数:
public Func<int,int,int,int> AddThreeNumber() { return (x, y, z) => x + y + z; }
局部套用版本:
public Func<int,Func<int,Func<int,int>>> AddThreeNumberCurrying() { Func<int, Func<int, Func<int, int>>> addCurring = x => y => z => x + y + z; return addCurring; }
调用过程:
[Test] public void Three_number_add_test() { //Arrange var curringReasoning = new CurryingReasoning(); //Act var result1 = curringReasoning.AddThreeNumber()(1, 2, 3); var curringResult = curringReasoning.AddThreeNumberCurrying()(1); var curringResult2 = curringResult(2); var result2 = curringResult2(3); //Assert result1.Should().Be(6); result2.Should().Be(6); }
当函数参数多了之后,手动局部套用越来越不容易写,我们可以利用扩展方法自动局部套用:
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func) { return x => y => func(x, y); } public static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3,TResult> func) { return x => y => z=>func(x, y,z); }
同样的道理,Action<>签名的函数也可以自动套用
有了这些扩展方法,使用局部套用的时候就更加easy了
[Test] public void Should_auto_curry_two_number_add_function() { //Arrange var add = _curringReasoning.AddTwoNumber(); var addCurrying = add.Curry(); //Act var result = addCurrying(1)(2); //Assert result.Should().Be(3); }
好了,局部套用就说到这里,stackoverflow有几篇关于currying使用的场景和定义的文章,大家可以继续了解。
函数式编程还有一些重要的思想,例如:纯函数的缓存,所为纯函数是指函数的调用不受外界的影响,相同的参数调用得到的值始终是相同的。尾递归,单子,代码即数据(.net中的表达式树),部分应用,组合函数,这些思想有的我也仍然在学习中,有的还在思考其最佳使用场景,所以不再总结,如果哪天领会了其思想会补充。
最后我还是想设计一个场景,把高阶函数,lambda表达式,泛型方法结合在一起,我之所以设计这样的例子是因为现在很多的框架,开源的项目都有类似的写法,也正是因为各种技术和思想结合在一起,才有了极富有表达力并且非常优雅的代码。
需求:设计一个单词查找器,该查找器可以查找某个传入的model的某些字段是否包含某个单词,由于不同的model具有不同的字段,所以该查找需要配置,并且可以充分利用vs的智能提示。
这个功能其实就两个方法:
private readonly List<Func<string, bool>> _conditions; public WordFinder<TModel> Find<TProperty>(Func<TModel,TProperty> expression) { Func<string, bool> searchCondition = word => expression(_model).ToString().Split(' ').Contains(word); _conditions.Add(searchCondition); return this; } public bool Execute(string wordList) { return _conditions.Any(x=>x(wordList)); }
使用:
[Test] public void Should_find_a_word() { //Arrange var article = new Article() { Title = "this is a title", Content = "this is content", Comment = "this is comment", Author = "this is author" }; //Act var result = Finder.For(article) .Find(x => x.Title) .Find(x => x.Content) .Find(x => x.Comment) .Find(x => x.Author) .Execute( "content"); //Assert result.Should().Be(true); }
该案例本身不具有实用性,但是大家可以看到,正是各种技术的综合应用才设计出极具语义的api, 如果函数参数改为Expression
以上がC# 関数型プログラミングのサンプル コードの詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。