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

zzfx zzfx     2022-10-09     677

关键词:

目录

一个问题

假设现在我们需要开发一个绘制数学函数平面图像(一元)的工具库,可以提供绘制各种函数图形的功能,比如直线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准则,所以引入委托帮助我们像函数式编程那样去操作每个函数(方法)。

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

 

作者:周见智 
出处:http://www.cnblogs.com/xiaozhi_5638/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

为什么每个程序员都应该懂点前端知识?

【编者按】本文作者为OneAPM工程师李哲,文章主要介绍前端知识对于编程的必要性。这里说的前端知识是比较通俗的前端知识,包括网页,桌面或移动端程序的界面,命令行程序的提示等等,即和用户进行交... 查看详情

为啥不应该在函数式编程中使用变量赋值

】为啥不应该在函数式编程中使用变量赋值【英文标题】:Whyvariableassignmentsshouldnotusedinfunctionalprogramming为什么不应该在函数式编程中使用变量赋值【发布时间】:2017-07-0119:11:20【问题描述】:我正在学习函数式编程,我可以理... 查看详情

纯函数式编程

...始涉足函数式编程。据我了解,在纯函数范式中,函数不应该有条件,应该尽可能使用柯里化来分解。有人可以为我提供以下示例的“纯”功能版本吗?最好使用将成为功能范式一部分的所有严格技术:letrecgreatestCommonFactor 查看详情

javascript函数式编程(代码片段)

...、IO 等几种常见的Functor,或许很多看完第二篇文章的人都会有疑惑:『这些东西有什么卵用?』事实上,如果只是为了学习编写函数式、副作用小的代码的话,看完第一篇文章就足够了。第二篇文章和这里的第三篇着重于的... 查看详情

响应式编程函数式编程简介

...:关键字:lambda,lisp,大数据,AI理念:1.一切为函数2.每个函数是可靠的,没有副作用的;不能依赖外部环境/上下文3.每一个可靠小函数组合成大函数,再组成一个牛逼函数优点:1.适合并行计算,每个小函数分发给不同CPU。非... 查看详情

每个开发人员都应该知道的16个顶级新计算机编程语言

参考技术A函数式语言ElixirElixir比Erlang更容易编写,具有Haskell等语言的函数式编程概念。Elixir是基于Erlang虚拟机的,其广为人知的特点是运行低延时、分布式、可容错的系统,并成功用于Web开发与嵌入式软件领域。ElmElm是一种用... 查看详情

Javascript 是函数式编程语言吗?

...函数是一等对象,有闭包和更高阶的函数,Javascript是否应该被称为函数式编程语言?我认为它缺少的主要是纯函数,它不像其他函数式语言,比如lisp(尽管这并不是它不成为函数式语言的真正理由......)【问题讨论】:@sl 查看详情

在函数式编程中,是否有一种干净的方法可以对某些数据执行许多操作,而无需将数据显式传递到每个函数中?(代码片段)

假设我有一些函数可以对某些数据执行业务逻辑:functionaddEmployees(data,numberOfNewEmployees)//Businesslogic...data.employeeCount+=numberOfNewEmployees;returndata;functionwithdrawFunds(data,withdrawAmount)//Businesslogic. 查看详情

javascript函数式编程-包含闭包链式优化及柯里化

...好作用域局限、副作用少基本函数式编程://实现数组中每个单词首字母大写//一般写法constarr=[‘apple‘,‘orange‘,‘pear‘];for(constiinarr){constc=arr[i][0];arr[i]=c.toUpperCase()+arr[i]. 查看详情

什么是函数式编程(代码片段)

...体的函数还有各种限制,具体见链接。所以函数式编程也应该是一个从入参到返回值的黑盒子。概述并不是所有人在函数式编程的定义上达成了共识。一般来说,函数式编程是使用函数来编程的一种编程范式。但是这个定义并不... 查看详情

js函数式编程和递归探索:路由树的操作

...能理解会有些吃力。我会尽量把复杂问题简单化,争取让每个阅读的童鞋们都能看得懂。希望你对element-ui,vue-router,KeepAlive组件有一点了解。现在,我们开始吧。熟悉element-ui的童鞋们都知道,ElMenuItem和ElSubMenu都需要一个index属... 查看详情

Python 和函数式编程:有 apply() 函数吗?

...题描述】:Scala有apply()函数。我是Python新手,我想知道我应该如何编写以下单行代码:(part_a,part_b)=(lambdax:re.search(r"(\\w+)_(\\d+)",x) 查看详情

js函数式编程思想(v客学院知识分享)

...诸如RxJS(ReactiveX)等函数式框架的不断流行。函数式编程则应该是以函数做为舰载主体,然后对函数进行拆分封装、更加抽象,可扩展性极强。 与传统命令式函数相比存在那些优势? 语法精简清晰通用性更好维护及可扩展... 查看详情

scala函数式编程函数式的错误处理(代码片段)

...捕获并处理这个异常,有点不符合情理。更合理的处理,应该是让master接收到一个表示错误情况的消息,然后再决定接下来如何处理。而worker的异常就让worker自己去解决吧。而在scala中,有一种特定的类型,它用来 查看详情

函数式编程补充(代码片段)

1.map函数:(将列表里的每个元素都进行运算,最后得到原列表) r=[4,8,7,1]defcat(array):o=[]foriinr:o.append(i**2)returnov=cat(r)print(v) r=[4,8,7,1]defaddone(x):returnx+1defcat(func,array):#(func就是运行方法,array就是值)o=[]fori 查看详情

函数式编程(functionalprogramming)

...像面向对象的类概念一样把数据和函数封在一起,而是让每个函数都不要去修改原有数据(不可变性),而且通过产生新的数据来作为运算结果(纯函数)。(三)为什么会流行?引用透明(Referentialtransparency),指的是函数的... 查看详情

scala函数式编程scala基础语法介绍

...不过这些语法知识记不住也没关系,本身语法这种东西就应该在使用中被记住。这里写这篇的目的也只是梳理一遍,方便大家对语法有个 查看详情

函数式编程—高阶函数(代码片段)

...函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回reduce把一个函数作用在一个序列[x1,x2,x3,... 查看详情