函数式编程

函数式编程的三大特性:

 

  1. immutable data 不可变数据:像Clojure一样,默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。这样一来,可以让你的程序少很多Bug。因为,程序中的状态不好维护,在并发的时候更不好维护。(你可以试想一下如果你的程序有个复杂的状态,当以后别人改你代码的时候,是很容易出bug的,在并行中这样的问题就更多了)
  2. first class functions:这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回或是在函数中嵌套函数。这个有点像Javascript的Prototype(参看Javascript的面向对象编程
  3. 尾递归优化:我们知道递归的害处,那就是如果递归很深的话,stack受不了,并会导致性能大幅度下降。所以,我们使用尾递归优化技术——每次递归时都会重用stack,这样一来能够提升性能,当然,这需要语言或编译器的支持。Python就不支持。

函数式编程的技术:

 

  1. map & reduce :这个技术不用多说了,在很多语言都有,至少在我学的JavaScript和python中是有的,函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)这个很像C++中的STL中的foreach,find_if,count_if之流的函数的玩法。
  2. pipeline:这个技术的意思是,把函数实例成一个一个的action,然后,把一组action放到一个数组或是列表中,然后把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数所操作,最终得到我们想要的结果。
  3. recursing 递归 :递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。递归都可以转换成迭代,效率会更高但是代码比较难懂
  4. currying:有点类似python中的偏函数了,把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数。在C++中,这个很像STL中的bind_1st或是bind2nd。
  5. higher order function 高阶函数:所谓高阶函数就是函数当参数,比如说回调函数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。

函数式编程的优点:

  1. parallelization 并行:所谓并行的意思就是在并行环境下,各个线程之间不需要同步或互斥。
  2. lazy evaluation 惰性求值:这个需要编译器的支持。表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。这种思想在很多地方都有妙用,redis,python生成器等等
  3. determinism 确定性:所谓确定性的意思就是像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景的意思就是我们的函数会根据一些运行中的状态信息的不同而发生变化。

非函数式编程:

count = 0
def increment():
    global count
    count += 1

函数式编程:

def increment(count):
    return count + 1

函数式编程的准则:不依赖于外部数据,而且也不改变外部数据的值,而是返回一个新的值给你(没有副作用),原来一直不知道这个副作用到底会有什么副作用,现在觉得副作用虽然在单线程情况下没什么错,但是在并发环境下程序状态就变得很难控制

def inc(x):
    def incx(y):
        return x+y
    return incx
inc2 = inc(2)
inc5 = inc(5)
print inc2(5) # 输出 7
print inc5(5) # 输出 10

上面这个例子是不是有python偏函数內味了,int2 = functools.partial(int, base=2)。int2(10)=0b1010

这个技术其实就是上面所说的Currying技术。从这个技术上,你可能体会到函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。

函数式编程好处:

1)代码更简单了。
2)数据集,操作,返回值都放到了一起。
3)你在读代码的时候,没有了循环体,于是就可以少了些临时变量,以及变量倒来倒去逻辑。
4)你的代码变成了在描述你要干什么,而不是怎么去干。


函数式编程关注的是:describe what to do, rather than how to do it. 于是,我们把以前的过程式的编程范式叫做 Imperative Programming – 指令式编程,而把函数式的这种范式叫做 Declarative Programming – 声明式编程


怎么一步步改造成函数式编程:

过程式编程:

# random得到的是(0,1)的随机浮点数
from random import random

time = 5
car_index = [1,1,1]

while time:
    time -= 1
    print()
    for i in range(len(car_index)):
        if random() > 0.3:
            car_index[i] += 1
        print ('-' * car_positions[i])

进一步改造成函数:

from random import random
def move_cars():
    for i, _ in enumerate(car_positions):
        if random() > 0.3:
            car_positions[i] += 1


def draw_car(car_position):
    print('-' * car_position)


def run_step_of_race():
    global time
    time -= 1
    move_cars()


def draw():
    print()
    for car_position in car_positions:
        draw_car(car_position)


time = 5
car_positions = [1, 1, 1]


while time:
    run_step_of_race()
    draw()

从上面我们会发现:变成函数后,我们的代码逻辑也会变得几个小碎片,于是我们读代码时要考虑的上下文就少了很多,阅读代码也会更容易。而把代码逻辑封装成了函数后,我们就相当于给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的

但是,你会发现,封装成函数后,这些函数都会依赖于共享的变量来同步其状态。于是,我们在读代码的过程时,每当我们进入到函数里,一量读到访问了一个外部的变量,我们马上要去查看这个变量的上下文,然后还要在大脑里推演这个变量的状态, 我们才知道程序的真正逻辑。也就是说,这些函数间必需知道其它函数是怎么修改它们之间的共享变量的,所以,这些函数是有状态的。这就导致了函数有副作用,对代码重用,还是对代码的并行来说,都是有副作用的

函数式编程改造:

from random import random


def move_cars(car_positions):
    return map(lambda x: x + 1 if random() > 0.3 else x,
               car_positions)


def output_car(car_position):
    return '-' * car_position


def run_step_of_race(state):
    return {'time': state['time'] - 1,
            'car_positions': move_cars(state['car_positions'])}


def draw(state):
    print()
    print('\n'.join(map(output_car, state['car_positions'])))


def race(state):
    draw(state)
    if state['time']:
        race(run_step_of_race(state))

race({'time': 5, 'car_positions': [1, 1, 1]})

 pipeline

pipeline 管道借鉴于Unix Shell的管道操作——把若干个命令串起来,前面命令的输出成为后面命令的输入,如此完成一个流式计算。(管道绝对是一个伟大的发明,他的设哲学就是KISS – 让每个功能就做一件事,并把这件事做到极致,软件或程序的拼装会变得更为简单和直观。这个设计理念影响非常深远,包括今天的Web Service,云计算,以及大数据的流式计算等等)

 那么函数式编程中的pipeline怎么实现的呢?

我们先来看一个如下的程序,这个程序的process()有三个步骤:

1)找出偶数。
2)乘以3
3)转成字符串返回

def process(num):
    # filter out non-evens
    if num % 2 != 0:
        returnn None
    num = num * 3
    num = 'The Number: {}',format(num)
    return num


nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for num in nums:
    print(process(num))


# 输出:
# None
# The Number: 6
# None
# The Number: 12
# None
# The Number: 18
# None
# The Number: 24
# None
# The Number: 30

为了输出美观:

def even_filter(nums):
    for num in nums:
        if num % 2 == 0:
            yield num

def multiply_by_three(nums):
    for num in nums:
        yield num * 3

def convert_to_string(nums):
    for num in nums:
        yield 'The Number: {}'.format(num)

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 有reduce內味了是不是
pipeline = convert_to_string(multiply_by_three(even_filter(nums)))

for num in pipeline:
    print(num)

# 输出:
# The Number: 6
# The Number: 12
# The Number: 18
# The Number: 24
# The Number: 30

yield就是这个函数变成了生成器,这是典型的lazy evaluation

好了,根据前面的原则——“使用Map & Reduce,不要使用循环”,那我们用比较纯朴的Map & Reduce

from functools import *
def even_filter(nums):
    return filter(lambda x: x%2==0, nums)

def multiply_by_three(nums):
    return map(lambda x: x*3, nums)

def convert_to_string(nums):
    return map(lambda x: 'The Number: {}'.format(x),  nums)

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

pipeline = convert_to_string(multiply_by_three(even_filter(nums)))


for num in pipeline:
    print(num)

第二种实现方式:

pipeline_func(nums, [even_filter,
                     multiply_by_three,
                     convert_to_string])

 pipeline_func的实现:

def pipeline_func(data, fns):
    return reduce(lambda a, x: x(a),fns,data)

编程思想:函数式编程,OO编程 ,泛型编程,过程式编程

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值