函数式编程初识篇

   函数式编程是一种编程模型,它很古老,已经有了五六十年的发展历史。随着21世纪互联网的高速发展,函数式编程获得了越来越高的关注度。不仅最古老的语言Lisp重获新春,而且涌现出了一系列新的函数式语言,有Erlang、python、Haskell、clojure、Scala、F#等。而且在.NET和Java的高版本中,均对函数式编程有了支持和实现。在科学计算与并行程序中,通常能看到函数式编程的身影。作为一名合格的搬砖工,学习函数式编程的思想,能让你换一种思路解决问题,对于搬砖技能的提高有莫大的帮助。

什么是函数式编程

   根据维基百科的定义,函数式编程是一种编程模型,他将计算机运算看做是数学中函数的计算,并且避免了状态以及变量的概念

   它属于结构化编程的一种,主要思想是在函数中摒弃变量以及赋值操作,把运算过程尽量写成一系列嵌套的函数调用

   比如,要求在程序中实现数学表达式:(1+2)*3-4,传统的过程式编程可能会分三步来操作:var a = 1+2; var b = a*3; var c = b-4; 而用函数式编程实现方式为:var result = subtract(multiply(add(1,2), 3), 4);

   函数式编程语言有诸多优点,如代码简洁、便于编写与调试、方便并发编程等,也许函数式编程会成为下一个编程的主流范式。但函数式编程通常只适用于科学研究或并发编程等场景,用于开发CRUD程序实在太过牵强,这也是函数式语言很难被企业大规模应用的原因。

函数式编程的特点

   函数式编程有许多特点, 只有符合这些特点才算是真正的函数式编程。

1、支持高阶函数

   在数学和计算机科学中,高阶函数是指至少满足下列一个条件的函数:① 接受一个或多个函数作为输入; ② 输出一个函数。

   该特点也称为“第一等公民”(first class)原则,是指函数与其他数据类型处于同等地位,可以将函数赋值给其他变量,也可以作为另一个函数的参数,或作为另一个函数的返回值。在函数式编程中,函数是基本单位,几乎被用作一切,包括最简单的计算,甚至连变量都被计算所取代。

2、只用“表达式”,不用“语句”

   "表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。

   函数式编程要求,只使用表达式,不使用语句也就是说,每一步都是单纯的运算,而且都有返回值。原因是函数式编程的开发动机,一开始就是为了处理运算(computation),不考虑系统的读写(I/O)。"语句"属于对系统的读写操作,所以就被排斥在外。

   当然,实际应用中,不做I/O是不可能的。因此,编程过程中,函数式编程只要求把I/O限制到最小,不要有不必要的读写行为,保持计算过程的单纯性。

3、变量不变性、无状态

   由于函数式编程没有使用语句,也就是没有赋值操作,因此它不会修改系统变量,只单纯地返回计算值。在函数式编程中,变量只是一个名称,而不是一个存储单元,这是函数式编程与传统的命令式编程最典型的不同之处

   在数学中,函数f(x)=y的结果无论在何种场景下都是相同的,这就是函数的一致性。包含赋值语句的程序在并发场景下可能会产生不同的结果,因此函数式编程取消了赋值模型,使得数学模型与编程模型完美地达成了统一。

   在其他语言中,状态通常保存在变量中,而函数式编程不修改变量,在需要传递状态的时候通常通过函数参数的形式进行,因此递归是函数式编程中一个很常用的表现形式

4、引用透明

   函数式的运行只依赖于参数,不依赖于任何的外部变量或其他状态,因此函数式编程的引用是非常透明的,在任何场景中,只要输入的参数相同,函数就会得到一致的运行结果。

函数式编程与递归、尾递归

   递归是函数式编程中一个非常重要的概念,函数式编程可以通过递归将一个大的问题无限分解成多个小问题。

   循环模型是在描述我们该如何去解决问题,而递归模型更多地在描述这个问题的定义。

   斐波那契数列是一个经典的数学模型,我们可以用循环模型和递归模型分别来解决这个问题。

   循环模型解决思路:求a,b斐波那契数列的第n个数的值,等价于求b,a+b斐波那契数列的第n-1个值....直到n=0或n=1即得结果,python实现代码如下:

 1 def Fib(n): 
 2     a=0 
 3     b=1 
 4     n = n - 1 
 5     while n>0: 
 6         temp=b 
 7         b=a+b 
 8         a=temp 
 9         n = n-1 
10     return a

   递归模型解决思路:将f(n)的求值问题分解为f(n-1)和f(n-2)的问题...直到n=1或n=1即得结果,python实现代码如下:

1 def Fib(a): 
2     if a==0 or a==1: 
3         return a
4     else: 
5         return Fib(a-2)+Fib(a-1)

   由此可以看出,递归模型在描述斐波那契问题,相比循环模型,具有更好的可读性。但是当递归的层数过多时,有可能引发栈溢出StackOverflow,而循环则不会发生此问题。但是函数式编程具有变量不可变性,难道函数式编程就没有栈安全的递归方式吗?

   答案是肯定的,那就是:尾递归。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。也就是说,尾递归内部函数的调用结果就是整个函数的返回值,并且内部递归函数不在表达式中。

   为什么尾递归不会引起栈溢出?其实尾递归的原理就是不保持当前递归函数的状态,而把需要保持的东西全部用参数给传到下一个函数里,这样就可以自动清空本次调用的栈空间,不会引起多层栈的嵌套。对于大多数函数式语言的编译器来说,他们对尾递归提供了优化,使尾递归可以优化成循环迭代的形式,提高运行速度

   使用尾递归来解决斐波那契问题,就不会引起栈溢出的问题,python实现代码如下:

1 def Fib(a,b,n): 
2     if n==0 or a==1: 
3         return a
4     else: 
5         return Fib(a,a+b,n-1)

   仔细分析,不难发现这个尾递归解决思路与上述循环模型解决思路是一致的,可以说,尾递归模型只是循环模型的一种无变量的表现形式。因此,尾递归的本质就是“伪递归”。

函数式编程有什么意义

   为什么函数式编程受到的关注度越来越高,它有哪些意义?

1、代码简洁、开发快速

   函数式编程大量使用函数的概念,大量使用表达式代替语句,因此程序代码通常很短,开发速度较快。

   Paul Graham在《黑客与画家》一书中写道:同样功能的程序,极端情况下,Lisp代码的长度可能是C代码的二十分之一。

2、接近自然语言,易于理解

   函数式编程解决问题思考方式为我要“干什么”,而不是“怎么干”,因此非常接近自然语言,易于理解。

   如前面提到的将数学表达式:(1 + 2) * 3 - 4 用函数式编程实现为:subtract(multiply(add(1,2), 3), 4),非常容易理解。

3、更方便的代码管理

   函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。

4、易于并发编程

   函数式编程不引用外部变量,也不修改变量,因此在并发运行中根本不用考虑“锁”的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"。

   在多核CPU的时代,函数式编程的该特性将体现非常大的优势。

原文地址:https://www.cnblogs.com/hanganglin/p/4316505.html