转自: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. 防CSRF攻击:一场由重复提交的问题引发的前端后端测试口水战

    重复提交,这是一直以来都会存在的问题,当在网站某个接口调用缓慢的时候就会有可能引起表单重复提交的问题,不论form提交,还是ajax提交都会有这样的问题,最近在某社交app上看到这么一幕,这个团队没有 ...

  2. Unity投影器细节整理

    抽了个空整理下投影器 一般投影器需要两张贴图,一张Cookie,一张FallOff. Unity提供Light和Multiple两种自带shader,和粒子类似. Cookie需要非alpha贴图,F ...

  3. python geoip2使用

    使用geoip可以查询ip的详细地址信息,简单记录下使用方法(centos python2.7): 1.安装 yum -y install geoip geoip-devel pip install ...

  4. Spring Cloud内置的Zuul过滤器详解

    Spring Cloud默认为Zuul编写并启用了一些过滤器,这些过滤器有什么作用呢?我们不妨按照@EnableZuulServer.@EnableZuulProxy两个注解进行展开,相信大家对这两个 ...

  5. 安卓数据解析之 fastjson 的解析以及Gson解析

    在安卓开发过程中的.我们经常使用的数据传递是以json格式传递.安卓 亲爹提供了我们Gson解析工具.点击下载Gson.jar 阿里巴巴FastJson是一个Json处理工具包,包含"序列化 ...

  6. nodejs中的框架介绍

    Sequelize 关系型数据库对象模型映射框架 mongoose 非关系型数据库文档模型映射框架

  7. [SQL in Azure] Configure a VNet to VNet Connection

    http://msdn.microsoft.com/en-us/library/azure/dn690122.aspx Configure a VNet to VNet Connection 2 ou ...

  8. zipkin微服务调用链分析

    1.zipkin的作用 在微服务架构下,一个http请求从发出到响应,中间可能经过了N多服务的调用,或者N多逻辑操作, 如何监控某个服务,或者某个逻辑操作的执行情况,对分析耗时操作,性能瓶颈具有很大价 ...

  9. 【工具】Sublime + MarkdownEditing + OmniMarkupPreviewer使用起来

    Package Control的安装 下载安装Sublime Text3后,View -> Show Console调用Console. 在Console输入以下代码安装: import url ...

  10. Android 微信支付资料收集

    老板要求支持微信支付,收集了些资料做后期参考 http://www.360doc.com/content/15/0214/10/7044580_448519997.shtml http://www.t ...