• 函数式编程


    昨天看RxJava时提到了函数式编程:

    今天在看极客时也遇到了讲解:

    虽然C语言简单灵活,能够让程序员在高级语言特性之上轻松进行底层上的微观控制,被誉为 高级语言中的汇编语言 ,
    但其基于过程和底层的设计初衷又成了它的短板.

    在程序世界中,编程工作更多的是解决业务上的问题,而不是计算机的问题,我们需要更为贴近业务,更为抽象的语言,如典型的面向对象语言C++和Java等.

    C++很大程度上解决了C语言中的各种问题和不便,尤其是通过类,模板,虚函数和运行时识别等解决了C语言的泛型编程问题.
    然而,如何做更为抽象的泛型呢?答案就是函数式编程(Functional Programming)

    相对于计算机的历史而言,函数式编程其实是一个非常古老的概念.函数式编程的基础模型来源于λ演算,而λ演算并没有被设计在计算机上执行.
    我们来看一下函数式编程,它的理念就来自于数学中的代数

    f(x)=5x^2+4x+3
    g(x)=2f(x)+5=10x^2+8x+11
    h(x)=f(x)+g(x)=15x^2+12x+14
    

    假设f(x)是一个函数,g(x)是第二个函数,把f(x)这个函数套下来,并展开.然后还可以定义一个由两个一元函数组合成的二元函数,还可以做递归,下面这个函数定义就是斐波那契数列

    f(x)=f(x-1)+f(x-2)
    

    对于函数式编程来说,它只关心定义输入数据和输出数据相关的关系,数学表达式里面其实是在做一种映射(mapping),
    输入的数据和输出的数据关系是什么样的,是用函数来定义的.

    函数式编程有以下特点:

    特征

    • stateless: 函数不维护任何状态.函数式编程的核心精神是stateless,简而言之就是它不能存在状态,打个比方,你给我数据我处理完扔出来.里面的数据是不变的.
    • immutable: 输入数据是不能动的,动了输入数据就有危险,所以要返回新的数据集.

    优势:

    • 没有状态就没有伤害
    • 并行执行无伤害
    • Copy-Paste重构代码无伤害
    • 函数的执行没有顺序上的问题

    函数式编程还带来了以下一些好处:

    • 惰性求值:
      这需要编译器的支持,表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值.
      也就是说,语句如x:=expression;(把一个表达式的结果赋值给一个变量)显式地调用这个表达式被计算并把结果放置到x中,
      但是先不管实际在x中的是什么,直到通过后面的表达式中到x的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,
      最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树.

    • 确定性:
      所谓确定性,就是像在数学中那样,f(x)=y这个函数无论在什么场景下,都会得到同样的结果,而不是像程序中的很多函数那样.同一个参数,在不同
      的场景下会计算出不同的结果,这个我们称之为函数的确定性.所谓不同的场景,就是我们的函数会根据运行中的状态信息的不同而发生变化.

    We know,because of the "state",在并行执行和copy-paste时引发bug的概率是非常高的,所以没有状态就没有伤害,就像没有依赖就没有伤害一样.
    并行执行无伤害,copy代码无伤害,因为没有状态,代码怎样copy都行.

    劣势:

    • 数据复制比较严重
      有一些人可能会觉得这会对性能造成影响,其实,这个劣势不见得会导致性能不好.因为没有状态,所以代码在并行上根本不需要锁(不需要对状态修改的锁),
      所以可以拼命地并发,反而可以让性能很不错. 比如: Erlang就是其中的代表.

    对于纯函数式(也就是完全没有状态的函数)的编程来说,各个语言支持的程度如下:

    • 完全纯函数式 Haskell
    • 容易写纯函数 F#,Ocaml,Clojure,Sala
    • 纯函数需要花点精力 C#,Java,JavaScript

    完全纯函数的语言呢,很容易写成函数,纯函数需要花精力.只要所谓的纯函数的问题,传进来的数据不改,改完的东西复制一份拷出去,然后没有状态显示.
    But most people 并不习惯函数式编程,因为函数式编程和过程式编程的思维方式完全不同.
    过程式编程是在把具体的流程描述出来,所以可以不假思索,而函数式编程的抽象度更大,在实现方式上,有
    函数套函数, 函数返回函数, 函数里定义函数 , 把人搞得confused

    函数式编程用到的技术:
    下面是函数式编程用到的一些技术:

    • first class function (头等函数) :
      这个技术可以让你的函数就像变量一样来使用. 也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回.
      或是在函数中嵌套函数.
    • tail recursion optimization (尾递归优化) :
      递归的害处呢在于如果递归很深的话呢,stack受不了,并导致性能大幅度下降,因此,我们使用尾递归优化技术--每次递归时都重用
      stack,这样能够提升性能哦.当然这需要语言或编译器的支持呢. Python 就不支持.
    • map & reduce :
      这个技术不用多说了,函数式编程最常见的技术就是对一个集合做Map和Reduce操作了,这比起过程式的语言来说,在代码上要更容易阅读
      (传统过程式的语言需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)这个很像C++STL中foreach啊,find_if啊,count_if等函数的玩法.
    • pipeline (管道) :
      这个技术的意思是,将函数实例成一个一个的action,然后将一组action放到一个数组或是列表中,再把数据传给这个action list,数据就像一个pipeline一样
      顺序地被各个函数所操作,最终得到我们想要的结果.
    • recursing (递归) :
      递归最大的好处呢就是简化代码,它可以把一个复杂的问题用很简单的代码描述出来. 注意:递归的精髓是描述问题,而这正是函数式编程的精髓.
    • currying (柯里化) :
      将一个函数的多个参数分解成多个函数,然后将函数多层封装起来,每层函数都返回一个函数去接收下一个参数,这可以简化函数的多个参数.
      在C++中呢,这很像STL中的bind1st或是bind2nd.
    • higher order function (高阶函数) :
      所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数,现象上就是函数传进传出,就像面向对象对象满天飞一样.
      这个技术用来做Decorator很不错呢.

    !!!!

    函数式编程的思维方式
    前面提到多次,函数式编程关注的是: describe what to do,rather than how to do it.
    于是,我们把以前的过程式编程范式叫做 Imperative Programming - 指令式编程,而把函数式编程范式叫做 Declarative Programming - 声明式编程.

    传统方式的写法:
    下面我们看一下相关的示例.
    比如,我们有3辆车比赛,简单起见,我们分别给这3辆车70%的概率让它们可以往前走一步,一共5次机会.然后打出这一次这3辆车的前行状态:
    对于 Imperative Programming 来说,代码如下: Python

    from random import random
    
    time = 5
    car_positions = [1,1,1]
    
    while time:
          # decrease time
          time -= 1
          
          print ''
          for i in range(len(car_positions)):
                # move car
                if random() > 0.3:
                      car_positions[i] += 1
    
                # draw car
                print '-' * car_positions[i]
    

    经过函数模块化: 更容易地阅读代码:

    
    from random import random
    
    time = 5
    car_positions = [1,1,1]
    
    def run_step_of_race():
          global time
          time -= 1
          move_cars()
    
    def move_cars():
          for i, _ in enumerate(car_positions):
                if random() > 0.3:
                      car_positions[i] += 1
    
    def draw():
          print ''
          for car_position in car_positions:
                draw_car(car_position)
    
    def draw_car(car_position):
          print '-' * car_position
    
    

    上面代码,从主循环开始,可以清楚看到程序主干,因为哦我们把程序的逻辑分成了几个函数. 这样一来呢
    代码逻辑就会变成几个小碎片,于是我们读代码时要考虑上下文就少了很多,阅读代码也会更容易.
    不像第一个示例.
    而将代码逻辑封装成函数后,我们就相当于给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的.
    但是你会发现哦,封装成函数后,这些函数都会依赖于共享的变量来同步其状态.
    于是在读代码过程时,每当进入到函数里,读到访问了一个外部的变量时,我们马上要去查看这个变量的上下文,
    然后还要在大脑里推演这个变量的状态,才能知道程序的真正逻辑.也就是说,
    这些函数必须知道其它函数是怎样修改它们之间共享变量的,所以这些函数是有状态的.

    函数式的写法 见下篇:
    https://www.cnblogs.com/ukzq/p/13770596.html

  • 相关阅读:
    redis持久化,主从及数据备份
    验证redis的快照和AOF
    Erlang中日志管理
    erlang tcp发包速度测试
    树(234树插入查找显示)
    JDBC(初步试用)
    树(二叉树的插入删除查找遍历)
    哈希表(链地址法插入删除)
    哈希表-再哈希法插入删除
    哈希表-线性探测插入删除
  • 原文地址:https://www.cnblogs.com/ukzq/p/13767952.html
Copyright © 2020-2023  润新知