函数式编程(2) 高阶函数
上一篇博客介绍了函数式编程中的基础知识:
1)什么是编程范式;
2)编程函数与数学函数的关系。
上篇文章介绍了函数式编程属于声明式编程范式中的一种,它仿照数学概念中的公式演算去解决问题,是一种更接近数学语言的编程方式。并且我们知道函数式编程中所有的函数都是“纯函数(Pure Function)”,因为只有纯函数才符合数学中对函数的定义,即:
1)函数均有输入(均带有参数)、均有输出(函数有返回值);
2)使用相同参数调用函数,得到的返回值无论何时均相等(不受其他因素影响)。
数学函数中包含一类函数叫“高阶函数”,它指“接收一个(多个)函数作为输入,或者返回一个函数”的函数。在函数式编程中,同样存在这样的高阶函数。只要一个函数包含有一个(多个)函数作为参数,或者返回另外一个函数,那么这个函数就称为“高阶函数”。在.NET中我们使用委托来封装方法,这样方法就可以像普通类型一样作为程序之间传递的参数、返回值。在.NET中已经有很多场合使用委托作为函数的参数,比如在异步编程时调用的一些方法均带有AsyncCallback委托类型的参数(BeginInvoke等),尤其是C#3.0出现之后,我们在使用一些类似Select()、Where()等扩展方法时,这些方法均会包含一个委托类型的参数:
1 string[] names = { "abc", "def", "ghi", "jkl", "mno" }; 2 IEnumerable<string> query = names 3 .Where(n => n.Contains("a")) 4 .OrderBy(n => n.Length) 5 .Select(n => n.ToUpper()); 6 foreach (string name in query) Console.WriteLine(name);
注意上面代码中使用Lambda表达式就是快速创建委托的一种方式。并且每个委托的签名几乎都一致:包含输入参数,有返回值。
到现在为止,我们很少碰到返回值是委托类型的函数。并不是没有这样的函数,只能说C#在容纳“函数式编程”的程度还不是很够。我们完全可以自己编写一个返回委托类型的“高阶函数”,比如数学中为一个函数求导函数的过程:
1 public delegate double Function1X(double x); //一元函数 2 public Function1X GetDerivative(Function1X func) //高阶函数,函数作为输入、返回值 3 { 4 double deltaX = 0.00000001; 5 return x => (func(x+deltaX)-func(x))/deltaX; //导数定义(近似) 6 }
如上代码所示,GetDerivative()方法包含一个委托类型参数,代表需要求导函数的函数;并且返回一个委托类型,代表求得的导函数。GetDerivative()方法既包含函数作为参数,又能返回一个函数,因此它属于“高阶函数”。
总结:
1)在编程中,我们可以使用“纯函数”来代表一个数学函数。“纯函数”无副作用(Side-Effect),并且符合数学中对函数的定义。可以这么说,编程函数涵盖的范围包含数学函数;
2)如果一个纯函数的参数又是一个函数,或者该纯函数能够返回另一个函数,那么这个纯函数就称为“高阶函数”,它与数学中的高阶函数对应。
到目前为止,我所讲到的所有内容都是为了让你在“程序”和“数学”之间找到一个共同点,能够一一类比。而这个过程中,“纯函数”无疑是重点。
下面分享一个demo,能够绘制任意给定函数的曲线图,并能够绘制指定点(X)处的切线。demo中主要演示一个求导函数的高阶函数和一个求切线函数的高阶函数:
1 /// <summary> 2 /// 求导函数 近似 3 /// </summary> 4 /// <param name="func"></param> 5 /// <returns></returns> 6 private Function1X Get(Function1X func) 7 { 8 double delatX = 0.00000000001; 9 return x => (func(x + delatX) - func(x)) / delatX; 10 } 11 12 /// <summary> 13 /// 根据斜率和(x,y)得到切线方程 14 /// </summary> 15 /// <param name="k"></param> 16 /// <param name="x"></param> 17 /// <param name="y"></param> 18 /// <returns></returns> 19 private Function1X Get2(double k, double x, double y) 20 { 21 // y = kx + b 22 // b = y - kx 23 double b = y - k * x; 24 return a => k * a + b; 25 }
(demo中解析函数表达式的过程使用到了老外的方法,站在巨人肩膀上:))下面是效果图:
源码下载地址:https://files.cnblogs.com/xiaozhi_5638/Functional_Program.rar