함수형 프로그래밍을 언급할 때 누구나 유연성과 동적 구문을 갖춘 LISP, Haskell과 같은 고대 함수형 언어를 떠올려야 합니다. 최근에는 Ruby, JavaScript, F#도 함수형 프로그래밍에 널리 사용되는 언어입니다. 그러나 .net은 람다 식을 지원하므로 C#은 명령형 프로그래밍 언어이지만 함수형 프로그래밍에서는 열등하지 않습니다. C#으로 코드를 작성하는 과정에서 의도적으로 또는 의도하지 않게 고차 함수, 결합 함수, 순수 함수 캐싱과 같은 아이디어를 사용하게 됩니다. 다음으로 우리는 일반적으로 사용되는 함수형 프로그래밍 시나리오를 요약하여 프로그래밍 프로세스에서 이러한 기술을 유연하게 적용하고 디자인 아이디어를 확장하며 코드 품질을 향상시키는 데 도움이 될 것입니다.
일반인의 용어로 고차 함수: 함수를 매개변수로 사용하는 함수를 고차 함수라고 합니다. 이 정의에 따르면 LINQ 식, Where, Select, SelectMany, First 및 기타 .net에서 광범위하게 사용되는 메서드는 모두 고차 함수입니다. 그러면 우리가 직접 코드를 작성할 때 언제 이 디자인을 사용하게 될까요?
예: 부동산 비용을 계산하는 함수, var fee=square*price를 설계하고, 면적(제곱)은 부동산의 성격에 따라 다른 방식으로 계산됩니다. 민간 주거지, 상업용 주거지 등에 다양한 계수를 곱해야 하는데, 이러한 요구에 따라 다음과 같은 기능을 설계하려고 합니다.
주거지역:
아아아아상업 및 주거 지역:
rree이러한 함수에는 모두 Func31803993a3bae4f7d54758d225c3e107이라는 공통 서명이 있으므로 이 함수 서명을 사용하여 부동산 비용 계산을 위한 함수를 설계할 수 있습니다.
public Func<int,int,decimal> SquareForCivil() { return (width,hight)=>width*hight; }
테스트를 작성하고
아아아아C#에서는 실행 중에 엄격한 평가 전략을 사용합니다. 소위 엄격한 평가는 매개변수가 함수에 전달되기 전에 평가된다는 의미입니다. 아직 설명이 조금 불분명한가요? 시나리오를 살펴보겠습니다. 실행해야 하는 작업이 있는데, 이를 위해서는 현재 메모리 사용량이 80% 미만이어야 하며 이전 계산 결과는
이 요구 사항을 충족하는 C# 코드를 빠르게 작성할 수 있습니다.
public Func<int, int, decimal> SquareForBusiness() { return (width, hight) => width * hight*1.2m; }
NextStep을 실행할 때 첫 번째 단계(BigCalculationForFirstStep 함수)의 메모리 사용량과 계산 결과를 전달해야 합니다. 코드에서 볼 수 있듯이 첫 번째 단계 작업은 시간이 많이 소요되지만 엄격한 평가 전략으로 인해 C#의 문에 대해 (memoryUtilization03b8972e6de7a8c041bd33c176032d7f 클래스가 필요한 경우 이 메커니즘을 사용할 수 있습니다.
커링은 부분 적용이라고도 합니다. 정의: 여러 매개변수를 받는 함수를 단일 매개변수(원래 함수의 첫 번째 매개변수)를 받는 함수로 변환하고, 나머지 매개변수를 받아 결과를 반환하는 새로운 함수를 반환하는 기술입니다. PS: 왜요? 공식 설명이 너무 헷갈리나요?
이러한 정의를 보면 이것이 무엇인지 모두가 이해하기 어려울 것이므로 카레의 원리부터 시작하겠습니다.
두 개의 숫자를 더하는 함수를 작성하세요:
[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); }
좋아요, 이 기능을 어떻게 사용하나요?
아아아아1+2=3, 호출은 매우 간단합니다. 요구사항을 업그레이드하려면 매개변수(숫자) 입력이 필요하고 10 + 입력 매개변수(숫자)의 결과를 계산하는 함수가 필요합니다. 누군가는 위의 코드가 이 요구사항을 완전히 충족할 수 있다고 말할 것 같은데, 첫 번째 매개변수로 10을 전달하면 끝입니다. 다른 사람들은 또 다른 오버로드를 작성하려면 하나의 매개변수만 필요하다고 말할 수도 있지만 실제 상황은 다른 사람들이 제공하는 API를 호출하고 있어 오버로드를 추가할 수 없습니다. 부분 적용의 사용 시나리오는 그다지 일반적인 시나리오가 아니라는 것을 알 수 있으므로 적절한 장면에 적절한 기술을 일치시키는 것이 가장 좋은 디자인입니다. 부분 적용의 구현을 살펴보겠습니다.
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"); } }
x => y => 유형의 함수로 얻은 함수 서명입니다. 이 시점에서 다시 전화하면:
//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); }
同样的道理,Actiona8093152e673feb7aba1828c43532094签名的函数也可以自动套用
有了这些扩展方法,使用局部套用的时候就更加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, 如果函数参数改为Expression7e21a607b5f8b4a44e84d6cad53bd045> 类型,我们还可以读取到具体的属性名称等信息。
위 내용은 C# 함수형 프로그래밍 샘플 코드에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!