每一个人都应该懂点函数式编程

目录html

一个问题算法

假设如今咱们须要开发一个绘制数学函数平面图像(一元)的工具库,能够提供绘制各类函数图形的功能,好比直线f(x)=ax+b、抛物线f(x)=ax²+bx+c或者三角函数f(x)=asinx+b等等。那么怎么设计公开接口呢?因为每种行数的系数(a、b、c等)不一样,而且函数构造也不一样。正常状况下咱们很难提供一个统一的接口。因此会出现相似下面这样的公开方法:编程

//绘制直线函数图像
public void DrawLine(double a, double b)
{
    List<PointF> points = new List<PointF>();
    for(double x=-10;x<=10;x=x+0.1)
    {
        PointF p =new PointF(x,a*x+b);
        points.Add(p);
    }
    //将points点链接起来
}
//绘制抛物线图像
public void DrawParabola(double a, double b, double c)
{
    List<PointF> points = new List<PointF>();
    for(double x=-10;x<=10;x=x+0.1)
    {
        PointF p =new PointF(x,a*Math.Pow(x,2) + b*x + c);
        points.Add(p);
    }
    //将points点链接起来
}
...
DrawLine(3, 4);   //绘制直线
DrawParabola(1, 2, 3);    //绘制抛物线

若是像上面这种方式着手的话,绘制N种不一样函数就须要定义N个接口。很明显不可能这样去作。数组

(注,若是采用虚方法的方式,要绘制N种不一样函数图像就须要定义N个类,每一个类中都须要重写生成points的算法)编程语言

若是咱们换一种方式去思考,既然是给函数绘制图像,为何要将它们的系数做为参数传递而不直接将函数做为参数传给接口呢?是的,没错,要绘制什么函数图像,那么咱们直接将该函数做为参数传递给接口。因为C#中委托就是对方法(函数,这里姑且不讨论二者的区别)的一个封装,那么C#中使用委托实现以下:函数式编程

public delegate double Function2BeDrawed(double x);
//绘制函数图像
public void DrawFunction(Function2BeDrawed func)
{
    List<PointF> points = new List<PointF>();
    for(double x=-10;x<=10;x=x+0.1)
    {
        PointF p =new PointF(x,func(x));
        points.Add(p);
    }
    //将points点链接起来
}
...
Function2BeDrawed func = 
    (Function2BeDrawed)((x) => { return 3*x + 4;}); //建立直线函数
DrawFunction(func);  //绘制系数为三、4的直线
Function2BeDrawed func2 =
    (Function2BeDrawed)((x) => {return 1*Math.Pow(x,2) + 2*x + 3;}); //建立抛物线函数
DrawFunction(func2);  //绘制系数为一、二、3的抛物线
Function2BeDrawed func3 = 
    (Function2BeDrawed)((x) => {return 3*Math.Sin(x) + 4;}); //建立正弦函数
DrawFunction(func3);  //绘制系数为三、4的正弦函数图像

如上。将函数(委托封装)做为参数直接传递给接口,那么接口就能够统一。至于到底绘制的是什么函数,彻底由咱们在接口外部本身肯定。函数

将函数看做和普通类型同样,能够对它赋值、存储、做为参数传递甚至做为返回值返回,这种思想是函数式编程中最重要的宗旨之一。工具

注:上面代码中,若是以为建立委托对象的代码比较繁杂,咱们能够本身再定义一个函数接收a、b两个参数,返回一个直线函数,这样一来,建立委托的代码就不用重复编写。学习

函数式编程中的函数spa

在函数式编程中,咱们将函数也看成一种类型,和其余普通类型(int,string)同样,函数类型能够赋值、存储、做为参数传递甚至能够做为另一个函数的返回值。下面分别以C#和F#为例简要说明:

注:F#是.NET平台中的一种以函数式编程范式为侧重点的编程语言。举例中的代码很是简单,没学过F#的人也能轻松看懂。F#入门看这里:MSDN

定义:

在C#中,咱们定义一个整型变量以下:

int x = 1;

在F#中,咱们定义一个函数以下:

let func x y = x + y

赋值:

在C#中,咱们将一个整型变量赋值给另一个变量:

int x = 1;
int y = x;

在F#中,咱们照样能够将函数赋值给一个变量:

let func = fun x y -> x + y  //lambda表达式
let func2 = func

存储:

在C#中,咱们能够将整型变量存储在数组中:

int[] ints = new int[]{1, 2, 3, 4, 5};

在F#中,咱们照样能够相似的存储函数:

let func x = x + 1
let func2 x = x * x
let func3 = fun x -> x - 1    //lambda表达式
let funcs = [func; func2; func3]  //存入列表,注意存入列表的函数签名要一致

传参:

在C#中将整型数值做为参数传递给函数:

void func(int a, int b)
{
    //
}
func(1, 2);

在F#中将函数做为参数传递给另一个函数:

let func x = x * x  //定义函数func
let func2 f x =   //定义函数func2 第一个参数是一个函数
   f x
func2 func 100   //将func和100做为参数 调用func2

做为返回值:

在C#中,一个函数返回一个整型:

int func(int x)
{
    return x + 100;
}
int result = func(1);  //result为101

在F#中,一个函数返回另一个函数:

let func x =
   let func2 = fun y -> x + y
   func2             //将函数func2做为返回值
let result = (func 100) 1  //result为101,括号能够去掉

数学和函数式编程

函数式编程由Lambda演算得来,所以它与咱们学过的数学很是相似。在学习函数式编程以前,咱们最好忘记以前头脑中的一些编程思想(如学习C C++的时候),由于先后两个编程思惟彻底不一样。下面分别举例来讲明函数式编程中的一些概念和数学中对应概念关系:

注:关于函数式编程的特性(features)网上总结有不少,能够在这篇博客中看到。

1.函数定义

数学中要求函数必须有自变量和因变量,因此在函数式编程中,每一个函数必须有输入参数和返回值。你能够看到F#中的函数不须要显示地使用关键字return去返回某个值。因此,那些只有输入参数没有返回值、只有返回值没有输入参数或者二者都没有的函数在纯函数式编程中是不存在的。

2.无反作用

数学中对函数的定义有:对于肯定的自变量,有且仅有一个因变量与之对应。言外之意就是,只要输入不变,那么输出必定固定不变。函数式编程中的函数也符合该规律,函数的执行既不影响外界也不会被外界影响,只要参数不变,返回值必定不变。

3.柯里化

函数式编程中,能够将包含了多个参数的函数转换成多个包含一个参数的函数。好比对于下面的函数:

let func x y = x + y
let result = func 1 2  //result为3

能够转换成

let func x =
   let func2 = fun y -> x + y
   func2
let result = (func 1) 2   //result结果也为3,能够去掉括号

能够看到,一个包含两个参数的函数通过转换,变成了只包含一个参数的函数,而且该函数返回另一个接收一个参数的函数。最后调用结果不变。这样作的好处即是:讲一个复杂的函数能够分解成多个简单函数,而且函数调用时能够逐步进行。

其实同理,在数学中也有相似“柯里化”的东西。当咱们计算f(x,y) = x + y这个函数时,咱们能够先将x=1带入函数,获得的结果为f(1,y) = 1 + y。这个结果显然是一个关于y的函数,以后咱们再将y=2带入获得的函数中,结果为f(1,2) = 1 + 2。这个分步计算的过程其实就是相似于函数式编程中的“柯里化”。

4.不可变性

数学中咱们用符号去表示一个值或者表达式,好比“令x=1”,那么x就表明1,以后不能再改变。同理,在纯函数式编程中,不存在“变量”的概念,也没有“赋值”这一说,全部咱们以前称之为“变量”的东西都是标识符,它仅仅是一个符号,让它表示一个东西以后不能再改变了。

5.高阶函数

在函数式编程中,将参数为函数、或者返回值为函数的这类函数统称之为“高阶函数”,前面已经举过这样的例子。在数学中,对一个函数求导函数的过程,其实就是高阶函数,原函数通过求导变换后,获得导函数,那么原函数即是输入参数,导函数即是返回值。

混合式编程风格

过程式、面向对象再到这篇文章讲到的函数式等,这些都是不一样地编程范式。每种范式都有本身的主导编程思想,也就是对待同一个问题思考方式都会不一样。很明显,学会多种范式的编程语言对咱们思惟方式有很是大的好处。

不管是本文中举例使用到的F#仍是Java平台中的Scala,大多数冠名“函数式编程语言”的计算机语言都并非纯函数式语言,而是以“函数式”为侧重点,同时兼顾其余编程范式。就连曾经主打“面向对象”的C#和Java,现现在也慢慢引入了“函数式编程风格”。C#中的委托、匿名方法以及lambda表达式等等这些,都让咱们在C#中进行函数式编程成为可能。若是须要遍历集合找出符合条件的对象,咱们之前这样去作:

foreach(Person p in list)
{
    if(p.Age > 25)
    {
        //...
    }
}

如今能够这样:

list.Where(p => p.Age>25).Select(p => p.Name).toArray();

本篇文章开头提出的问题,采用C#委托的方式去解决,其实本质上也是函数式思想。因为C#必须遵循OO准则,因此引入委托帮助咱们像函数式编程那样去操做每一个函数(方法)。

本篇文章介绍有限,并无充分说明函数式编程的优势,好比它的不可变特性无反作用等有利于并行运算、表达方式更利于人的思惟等等。实质上博主本人并无参与过实际的采用函数式语言开发的项目,可是博主认为函数式思想值得咱们每一个人去了解、掌握。(本文代码手敲未验证,若有拼写错误见谅)

相关文章
相关标签/搜索