转自:http://www.cnblogs.com/xiaozhi_5638/p/4762846.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); //绘制系数为3、4的直线
Function2BeDrawed func2 =
(Function2BeDrawed)((x) => {return 1*Math.Pow(x,2) + 2*x + 3;}); //创建抛物线函数
DrawFunction(func2); //绘制系数为1、2、3的抛物线
Function2BeDrawed func3 =
(Function2BeDrawed)((x) => {return 3*Math.Sin(x) + 4;}); //创建正弦函数
DrawFunction(func3); //绘制系数为3、4的正弦函数图像

如上。将函数(委托封装)作为参数直接传递给接口,那么接口就可以统一。至于到底绘制的是什么函数,完全由我们在接口外部自己确定。

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

注:上面代码中,如果觉得创建委托对象的代码比较繁杂,我们可以自己再定义一个函数接收a、b两个参数,返回一个直线函数,这样一来,创建委托的代码就不用重复编写。

函数式编程中的函数

在函数式编程中,我们将函数也当作一种类型,和其他普通类型(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准则,所以引入委托帮助我们像函数式编程那样去操作每个函数(方法)。

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

函数式编程——C#理解的更多相关文章

  1. Js中函数式编程的理解

    函数式编程的理解 函数式编程是一种编程范式,可以理解为是利用函数把运算过程封装起来,通过组合各种函数来计算结果.函数式编程与命令式编程最大的不同其实在于,函数式编程关心数据的映射,命令式编程关心解决问 ...

  2. 面试官问:说说你对Java函数式编程的理解

    常见的面试问题 总结一下,在Java程序员的面试中,经常会被问到类似这样的问题: Java中的函数式接口是什么意思? 注解 @FunctionalInterface 的作用是什么? 实现一个函数式接口 ...

  3. 翻译连载 | 第 10 章:异步的函数式(下)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  4. Scala函数与函数式编程

    函数是scala的重要组成部分, 本文将探讨scala中函数的应用. scala作为支持函数式编程的语言, scala可以将函数作为对象即所谓"函数是一等公民". 函数定义 sca ...

  5. Python学习(26):Python函数式编程

    转自  http://www.cnblogs.com/BeginMan/p/3509985.html 前言 <core python programming 2>说: Python不大可能 ...

  6. 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何 ...

  7. javascript函数式编程和链式优化

    1.函数式编程理解 函数式编程可以理解为,以函数作为主要载体的编程方式,用函数去拆解.抽象一般的表达式 与命令式相比,这样做的好处在哪?主要有以下几点: (1)语义更加清晰 (2)可复用性更高 (3) ...

  8. 《深入理解Java函数式编程》系列文章

    Introduction 本系列文将帮助你理解Java函数式编程的用法.原理. 本文受启发于JavaOne 2016关于Lambda表达式的相关主题演讲Lambdas and Functional P ...

  9. 理解iOS与函数式编程

    有时候,一个关键字就是一扇通往新世界的大门.两年前,身边开始有人讨论函数式编程,拿关键字Functional Programming一搜,全是新鲜的概念和知识,顺藤摸瓜,看到的技术文章和框架也越来越多 ...

随机推荐

  1. 设计模式-单例模式(Singleton Pattren)(饿汉模式和懒汉模式)

    单例模式(Singleton Pattren):确保一个类在整个应用中只有一个实例,并提供一个全局访问点. 实现要点: 1. 私有化构造方法 2. 类的实例在类初始化的时候创建 3. 提供一个类方法, ...

  2. 我的Eclipse设置

    1.默认编码改成:UTF-8(在老项目里设置此项可能导致java源码文件注释显示乱码!可以手工输入GBK三个字母,然后点apply) 2.文件默认打开方式 3.背景颜色(#C0C0C0,RGB(192 ...

  3. zabbix 对/etc/ssh/sshd_config文件的监控 但status为unknowen

    原因为该文件没有被访问的权限: # ll /etc/ssh/sshd_config -rw------- root root Apr : /etc/ssh/sshd_config 授权后再看: [ro ...

  4. 10-free-must-read-books-machine-learning-data-science

    Spring. Rejuvenation. Rebirth. Everything’s blooming. And, of course, people want free ebooks. With ...

  5. Linux/CentOS关闭图形界面(X-window)和启用图形界面命令

    1.在图像界面关闭x window:1.1 shell中运行 init 3  进入文本模式,同时会关闭相关的服务(Xserver 肯定关闭)1.2 Alt+Ctrl+F1~F6到字符界面,root登陆 ...

  6. [MyBean-说明书]关于插件的单件模式(singleton),插件的共享模式

    [说明] 单件模式是一种用于确保整个应用程序中只有一个类实例. 想想我们的系统中有哪些方面可以应用到单件模式,比如大家常说的连接(ADOConnection)共享,其实就是指的单件模式. [MyBea ...

  7. 深入HBase架构解析(二)【转】

    转自:http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html 前言 这是<深入HBase架构解析(一)>的续,不多废话, ...

  8. Linux操作系统入门学习总结(2015.10)

    用了差不多45天的时间把Linux操作系统入门的资料学习了下.主要阅读了以下几本书: 鸟哥的私房菜:Linux基础学习篇(第三版) "Running Linux" <LINU ...

  9. 【linux】dpkg info修复及dpkg: warning: files list file for package

    mv /var/lib/dpkg/info /var/lib/dpkg/info.bak //现将info文件夹更名 sudo mkdir /var/lib/dpkg/info //再新建一个新的in ...

  10. linux 删除文件,df空间不变化

    今天遇到一个问题,就是linux服务器空间满了,可是删除了软件后. 查看空间,没有变化 ???啥情况 那么去查看删除的情况吧. [root@VM_0_4_centos usr]# lsof|grep ...