一个问题假设现在需要开发一个绘制数学函数平面图像(一元)的工具库,它可以提供绘制各种函数图形的功能,比如直线f(x)=ax+b,抛物线f(x)=ax2+bx+c或者三角函数f(x)=asinx+b等等。那么如何设计一个公共接口呢?因为每个行号的系数(a、b、c等)不同,函数结构也不同。一般情况下,我们很难提供统一的接口。所以就会有像下面这样的公共方法://绘制直线函数图像publicvoidDrawLine(doublea,doubleb){Listpoints=newList();for(doublex=-10;x<=10;x=x+0.1){PointFp=newPointF(x,a*x+b);points.Add(p);}//把点连接起来}//画一个抛物线图publicvoidDrawParabola(doublea,doubleb,doublec){Listpoints=newList();for(doublex=-10;x<=10;x=x+0.1){PointFp=newPointF(x,a*Math.Pow(x,2)+b*x+c);points.Add(p);}//连接点}...DrawLine(3,4);//画直线DrawParabola(1,2,3);//画一条抛物线如果像上面这样开始的话,需要定义N个接口来画出N个不同的函数。这样做显然是不可能的。(注意,如果使用virtual方法,需要定义N个类来绘制N个不同的函数图像,每个类都需要重写生成点的算法)如果我们换个角度想,既然是为了函数绘制图像,为什么要将它们的系数作为参数传递,而不是直接将函数作为参数传递给接口?对,没错,绘制什么函数图像,那我们直接把函数作为参数传给接口。由于C#中的delegate是对方法(函数,先不讨论两者的区别)的封装,那么C#中delegate的实现如下:publicdelegateddoubleFunction2BeDrawed(doublex);//绘制函数图像publicvoidDrawFunction(Function2BeDrawedfunc){Listpoints=newList();for(doublex=-10;x<=10;x=x+0.1){PointFp=newPointF(x,func(x));points.Add(p);}//连接点}...Function2BeDrawedfunc=(Function2BeDrawed)((x)=>{return3*x+4;});//创建一条直线functionDrawFunction(func);//画一条系数为3和4的直线Function2BeDrawedfunc2=(Function2BeDrawed)((x)=>{return1*Math.Pow(x,2)+2*x+3;});//创建抛物线函数DrawFunction(func2);//绘制系数为1,2,3的抛物线Function2BeDrawedfunc3=(Function2BeDrawed)((x)=>{return3*Math.Sin(x)+4;});//创建一个正弦函数DrawFunction(func3);//画一个系数为3和4的正弦函数图像如上。把函数(委托封装)直接作为参数传给接口,就可以统一接口了。至于绘制什么函数,完全由我们在界面之外决定。将函数视为普通类型是函数式编程最重要的原则之一,它可以被赋值、存储、作为参数传递,甚至作为返回值返回。注意:在上面的代码中,如果你觉得创建委托对象的代码比较复杂,我们可以再定义一个函数,接收a和b两个参数,返回一个直线函数。这样创建委托的代码就不用重复写了。函数式编程中的函数在函数式编程中,我们将函数视为一种类型。与其他常见类型(int、string)一样,函数类型可以被赋值、存储、作为参数传递,甚至可以用作另一个函数。返回值。下面以C#和F#为例进行简要说明:注:F#是.NET平台上的一种编程语言,侧重于函数式编程范式。示例中的代码非常简单,没有学过F#的人也能轻松理解。从这里开始使用F#:MSDN定义:在C#中,我们定义一个整型变量,如下所示:intx=1;在F#中,我们定义一个函数如下:letfuncxy=x+y赋值:在C#中,我们将一个整型变量赋值给另一个变量:intx=1;inty=x;在F#中,我们仍然可以将一个函数赋给一个变量:letfunc=funxy->x+y//lambda表达式letfunc2=func存储:在C#中,我们可以将整数变量存储在一个数组中:int[]ints=newint[]{1,2,3,4,5};在F#中,我们可以类似地存储函数:letfuncx=x+1letfunc2x=x*xletfunc3=funx->x–1//lambda表达式letfuncs=[func;func2;func3]//存储在列表中,注意签名存储在列表中的函数必须一致地传递:在C#中整数值作为参数传递给函数:voidfunc(inta,intb){//}func(1,2);在F#中,函数作为参数传递给另一个函数:letfuncx=x*x//定义函数funcletfunc2fx=//定义函数func2的第一个参数是一个函数fxfunc2func100//以func和100为参数调用func2作为返回值:在C#中,一个函数返回一个整数:intfunc(intx){returnx+100;}intresult=func(1);//resultis101在F#中,一个函数返回另一个函数:letfuncx=letfunc2=funy->x+yfunc2//使用函数func2作为返回值letresult=(func100)1//结果为101,括号可以去掉数学和函数式编程函数式编程是通过Lambda计算的,所以很类似于我们学过的数学。在学习函数式编程之前,我们一定要忘掉脑子里的一些编程思想(比如学习CC++的时候),因为前后两种编程思想是完全不同的。下面举例说明函数式编程中的一些概念与数学中相应概念的关系:注:关于函数式编程的特点网上有很多总结,可以看这篇博客。1.函数定义数学要求函数必须有自变量和因变量,所以在函数式编程中,每个函数都必须有输入参数和返回值。您可以看到F#中的函数不需要显式使用return关键字来返回值。因此,纯函数式编程中不存在只有输入参数没有返回值、只有返回值没有输入参数或两者都没有的函数。2、无副作用数学中函数的定义如下:对于某一自变量,对应于它的因变量只有一个。言外之意就是只要输入不变,输出就一定是固定的。函数式编程中的函数也符合这个规律。函数的执行既不影响也不被外界影响。只要参数保持不变,返回值也必须保持不变。3.在柯里化函数式编程中,一个包含多个参数的函数可以转换为多个包含一个参数的函数。例如对于下面的函数:letfuncxy=x+yletresult=func12//resultof3可以转换为letfuncx=letfunc2=funy->x+yfunc2letresult=(func1)2//resultresult也是3,可以去掉括号中看到,一个接受两个参数的函数被转换为一个接受一个参数的函数并返回另一个接受一个参数的函数。***调用结果不变。这样做的好处是可以将一个复杂的函数分解成多个简单的函数,函数调用可以分步进行。其实同理,数学中也有类似“柯里化”的东西。我们在计算函数f(x,y)=x+y时,可以先将x=1带入函数,结果就是f(1,y)=1+y。这个结果很明显是y的函数,然后我们把y=2代入到得到的函数中,结果就是f(1,2)=1+2。这个一步一步的计算过程其实类似于“currying“在函数式编程中。4.不变性在数学中,我们用符号来表示一个值或表达式,比如“设x=1”,那么x就代表1,之后就不能再改变了。同样,在纯函数式编程中,没有“变量”的概念,也没有“赋值”这样的东西。之前我们所说的“变量”都是一个标识符,它只是一个符号。Let表示事后无法更改。5、高阶函数在函数式编程中,这类以函数或返回值为参数的函数统称为“高阶函数”。在数学中,求导一个函数的过程其实就是一个高阶函数。原函数经过导数变换后,得到导数函数。那么原函数就是输入参数,导数函数就是返回值。本文提到的混合编程风格、过程式、面向对象和函数式,都是不同的编程范式。每个范式都有自己的主导编程思想,即对同一个问题的思考方式会有所不同。显然,学习一门具有多种范式的编程语言对我们的思维方式有很大的好处。无论是本文使用的F#还是Java平台上的Scala,大多数被称为“函数式编程语言”的计算机语言都不是纯粹的函数式语言,而是在注重“函数式”的同时兼顾其他编程范式。就连曾经“面向对象”的C#和Java,现在也在慢慢引入“函数式编程风格”。C#中的委托、匿名方法、lambda表达式,都让我们在C#中进行函数式编程成为可能。如果我们需要遍历集合来找到符合条件的对象,我们以前这样做:foreach(Personpinlist){if(p.Age>25){//...}}现在我们可以这样做:list.Where(p=>p.Age>25).Select(p=>p.Name).toArray();本文开头提出的问题是通过使用C#委托来解决的,其实本质上是一种函数式的思想。由于C#必须遵循OO原则,委托的引入帮助我们像函数式编程一样操作每一个函数(方法)。本文介绍有限,并没有充分说明函数式编程的优点,例如其不可变的特性和无副作用,有利于并行计算,表达方式更利于人的思维等。本质上,博主本人没有参与过使用函数式语言的实际开发项目,但博主认为函数式思维值得我们每个人去理解和掌握。