函数式编程思想

函数式编程思想

一、函数式编程简介

背景

  函数式编程诞生于50多年前。如今愈来愈多的人开始接受并进行函数式编程的实践。不只最古老的函数式语言 Lisp 重获青春,并且新的函数式语言层出不穷,好比 Erlang、clojure、Scala、F#等等。目前最当红的Objective-C, Python、Ruby、 Javascript都引入了对函数式编程的支持。就连老牌的面向对象的 Java、面向过程的 PHP, 以及苹果最新的swift语言,都忙不迭地加入匿名函数等机制。编程

  愈来愈多的迹象代表,函数式编程已经再也不是学术界的最爱,开始大踏步地在业界投入实用。 也许继面向对象编程以后,函数式编程会成为下一个编程的主流范式。swift

  它属于“结构化编程”的一种,主要思想是把运算过程尽可能写成一系列嵌套的函数调用。说到函数式编程编程就不得不说面向对象。面向对象是把一个功能的一组操做和相关数据封装在一个对象里,面向对象是对象满天飞。函数式编程是把一个功能的一个操做和相关数据封装在一块儿,函数式编程是函数满天飞。函数式编程比面向对象的优点就是粒度更小,生命周期更短。减小bug的有效途径就是减小变量的生命周期,缩小模块的粒度;因此函数式编程更不容易引入bug。数组

定义

  是一种编程范型,它将计算机运算视为数学上的函数计算,而且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。λ演算中最关键的要素就是函数被看成变量处理,可以参与运算。并发

价值观

  • 函数式编程强调程序的执行结果比执行过程更重要。关注于描述问题,而不是怎么实现,隐藏实现细节。
  • 化繁为简。利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算。
  • —你的优秀和个人人生无关,请带着你的趾高气扬滚蛋吧。

  下面是一个输出数组的例子  app

shoplist = ['apple','mango','carrot','banana']
print 'My shopping list is now', shoplist
#输出
#My shopping list is now ['banana', 'carrot', 'mango', 'rice']

  这样的代码更易读,代码只是描述在干什么,而不是如何作到这点的具体实现。若是是过程式编程,须要一个for循环去描述实现细节。编程语言

  函数式编程在解决复杂运算问题时,把一个问题分解为若干子问题,逐步求解。软件或程序的拼装会变得更为简单和直观。使代码更容易理解,方便排查问题,而且具备更好的可维护性和扩展性。ide

  如今有这样一个数学表达式:模块化

  (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); 

  这个就是函数式编程的准则:函数不受外部变量影响,不依赖于外部变量,也不改变外部变量的值

  传统的过程式编程:

int count;
void increment()
{
    returen count++;
}

  函数式编程:

def increment(count):
  return count+1;

  函数不访问全局变量,也不改变全局变量。

二、函数式编程特性

    封装、继承、多态是面向对象编程的三大特性。函数式编程也有本身的语言特性。

  • 数据不可变性(immutable data)多有的变量只能够赋值一次,变量不可变,若是想改变变量就建立一个新的变量。

 数据的不可变性保证了程序是无状态的,不少难解的bug每每是由各类复杂的状态引发的。好比发现某些状况下程序运行有问题,是某一个状态引发的,可是这个状态有100种可能性,在1000个地方都有对这个状态进行操做。debug的时候要杀人的心都有了。数据不可变性同时保证了函数没有“反作用”,函数的反作用是指除了返回函数值外,还对主调用函数

  • 函数是第一公民(first class method)函数能够像普通变量同样去使用。函数能够像变量同样被建立,修改,并当成变量同样传递,返回或是在函数中嵌套函数。
  • 引用透明(referential transparency) 指的是函数的运行不依赖于外部变量或“状态”,只依赖于输入的参数,任什么时候候只要参数相同,调用函数所获得的返回值老是相同的。自然适应并发编程,由于调用函数的结果具备一致性,因此根本不须要加锁,也就不存在死锁的问题。
  • 尾递归化(tail call optimization)由于函数调用要压栈保存现场,递归层次过深的话,压栈过多会产生性能问题。因此引入尾递归优化,每次递归时都会重用栈,提高性能。

 把函数做为参数传递的例子

NSComparisonResult (^cmp)(id obj1, id obj2)  = ^NSComparisonResult(id obj1, id obj2) {
    return [obj1 isEqualToString:obj2];
}
NSArray* items = [@"a", @"c", @"d"];
[items sortedArrayUsingComparator:cmp];

  sortedArrayUsingComparator方法负责排序,须要把比较的规则告诉他,将比较方法做为一个参数传到函数中进行运算。

  • 只用“表达式”,不用“语句”: 表达式是一个单纯的运算过程,老是有返回值;而语句是执行某种操做,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,并且都有返回值。缘由是函数是编程的开发动机,一是为了处理运算,不考虑系统的读写(I/O)。 
  • 没有“反作用”:所谓"反作用"(side effect),指的是函数内部与外部互动(最典型的状况,就是修改全局变量的值),产生运算之外的其余结果。函数式编程强调没有"反作用",意味着函数要保持独立,全部功能就是返回一个新的值,没有其余行为,尤为是不得修改外部变量的值。

—三、函数式编程技术(方法论)

  • —映射化简(map & reduce)函数式编程最多见的技术就是对一个集合作Map和Reduce操做。这比起过程式的语言来讲,在代码上要更容易阅读。传统过程式的语言须要使用for/while循环,而后在各类变量中把数据倒过来倒过去的
  • 管道    (pipeline)把一组函数放到一个数组或是列表中,而后把数据传给这个列表,数据就像一个pipeline同样顺序地被各个函数所操做,最终获得咱们想要的结果。他的设哲学就是让每一个功能就作一件事,并把这件事作到极致,软件或程序的拼装会变得更为简单和直观。
  • —递归    (recursing)递归最大的好处就简化代码,他能够把一个复杂的问题用很简单的代码描述出来。递归的精髓是描述问题,而这正是函数式编程的精髓。
  • —柯里化  (currying)把一个函数的多个参数分解成多个函数, 而后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数。
  • —高阶函数(higher order function)函数当参数,把传入的函数作一个封装,而后返回这个封装函数。现象上就是函数传进传出。

3.1.map & reduce

  Python代码:

def toUpper(item)
    return item.upper()
print map(tuUpper , [“hellow”,”world”])

  将数组里的全部字符串变为大写,直接使用map,不须要写for循环。最后输出 ["HELLO","WORLD"] 

print reduce(lambda x , y : x + y,[1,2,3,4,5])

  将数组里全部数值进行累加,至关于1+2+3+4+5,输出 15。

  lambda是Python的匿名函数,lambda x,y:x+y至关于def func(x,y):return x+y

3.2. 管道

  若是有一个需求找出一组数中的全部偶数并对他们求平方,最后求他们的和,能够分解成三个步骤:

  1)找偶数
  2)求平方
  3)累加

复制代码
def even(nums):
     return filter(lambda x: x%2==0, nums)
def square(nums):
    return map(lambda x: x*x, nums)
def  total(nums):
    return reduce(lambda x,y:x+y,nums)

nums = [1,2,3,4,5,6,7,8,9,10]
pipeline = total(square(even(nums)))
复制代码

  even方法求偶数,square求他们的平方,total方法将他们加在一块儿。经过管道的方式把他们串联在一块儿,一个复杂的处理就完成了。

  让每一个方法只作一件事,并把这件事作到极致。

3.3. 递归

  在其余类型的语言中,变量每每用来保存状态。变量不可变,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。

复制代码
void fun(const int i)
{
    if (i < 10 && i >= 0)
    {
        NSLog(@"i:%d", i);
        fun(i + 1);
    }
}
复制代码

  每次状态的变化就是值+1。

3.4.柯里化和高阶函数

  柯里化就是一个函数只有一个参数,那若是须要两个参数怎么办,好比两个数相加求和。经过把一个参数封装成函数的方式实现。  

复制代码
def func(a):
    def add(b):
        return a+b
    return add

funcA = func(5)
print funcA(10)
复制代码

  func函数返回一个add函数,funcA变量就是一个a为5的add函数。print funcA(10)就是向add函数传入10,最后结果就是5+10 输出15。

四、函数式编程意义

 一、代码简洁,快速开发

   函数式编程大量使用函数,减小了代码的重复,所以程序比较短,开发速度较快。

   二、接近天然语言,易于理解

   函数式编程注重干什么而不是怎么干,更容易理解。例子-----表达式(1 + 2) * 3 - 4,能够写成函数式语言:subtract(multiply(add(1,2), 3), 4)

 三、方便代码的管理和维护  

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

 四、易于进行并发开发

  函数式编程由于它不修改变量,因此根本不存在"锁"线程的问题,不须要考虑"死锁"(deadlock)。没必要担忧一个线程的数据,被另外一个线程修改,因此能够很放心地把工做分摊到多个线程中。

示例2:

var s1 = Op1();
 
var s2 = Op2();
 
var s3 = concat(s1, s2);

 

因为s1和s2互不干扰,不会修改变量,谁先执行是无所谓的,因此能够放心地增长线程,把它们分配在两个线程上完成。其余类型的语言就作不到这一点,由于s1可能会修改系统状态,而s2可能会用到这些状态,因此必须保证s2在s1以后运行,天然也就不能部署到其余线程上了。多核CPU是未来的潮流,因此函数式编程的这个特性很是重要。

 五、代码热升级

  函数式编程没有反作用,只要保证接口不变,内部实现是外部无关的。因此,能够在运行状态下直接升级代码,不须要重启,也不须要停机。Erlang语言早就证实了这一点,它是瑞典爱立信公司为了管理电话系统而开发的,电话系统的升级固然是不能停机的。

最后,其实使用面向对象或者面向方法都不重要,重要的是如何理解其中的价值观和方法论,构造可维护、可扩展、稳定又灵活的程序。

相关文章
相关标签/搜索