吾本轻狂|函数式编程( 三 )

我们可以把这个两重循环变成一些函数模块 , 这样有利于我们更容易地阅读代码:
from random import randomdef move_cars:for i, _ in enumerate(car_positions):if random > 0.3:car_positions[i] += 1def draw_car(car_position):print '-' * car_positiondef run_step_of_race:global timetime -= 1move_carsdef draw:print ''for car_position in car_positions:draw_car(car_position)time = 5car_positions = [1, 1, 1]while time:run_step_of_racedraw上面的代码 , 我们可以从主循环开始 , 我们可以很清楚地看到程序的主干 , 因为我们把程序的逻辑分成了几个函数 , 这样一来 , 我们的代码逻辑也会变得几个小碎片 , 于是我们读代码时要考虑的上下文就少了很多 , 阅读代码也会更容易 。 不像第一个示例 , 如果没有注释和说明 , 你还是需要花些时间理解一下 。 而把代码逻辑封装成了函数后 , 我们就相当于给每个相对独立的程序逻辑取了个名字 , 于是代码成了自解释的 。
但是 , 你会发现 , 封装成函数后 , 这些函数都会依赖于共享的变量来同步其状态 。 于是 , 我们在读代码的过程时 , 每当我们进入到函数里 , 一量读到访问了一个外部的变量 , 我们马上要去查看这个变量的上下文 , 然后还要在大脑里推演这个变量的状态 ,我们才知道程序的真正逻辑 。 也就是说 , 这些函数间必需知道其它函数是怎么修改它们之间的共享变量的 , 所以 , 这些函数是有状态的 。
我们知道 , 有状态并不是一件很好的事情 , 无论是对代码重用 , 还是对代码的并行来说 , 都是有副作用的 。 因此 , 我们要想个方法把这些状态搞掉 , 于是出现了我们的 Functional Programming 的编程范式 。 下面 , 我们来看看函数式的方式应该怎么写?
from random import randomdef 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_positiondef 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]})上面的代码依然把程序的逻辑分成了函数 , 不过这些函数都是functional的 。 因为它们有三个症状:
1)它们之间没有共享的变量 。
2)函数间通过参数和返回值来传递数据 。
3)在函数里没有临时变量 。
我们还可以看到 , for循环被递归取代了(见race函数)—— 递归是函数式编程中带用到的技术 , 正如前面所说的 , 递归的本质就是描述问题是什么 。
吾本轻狂|函数式编程Pipeline
pipeline 管道借鉴于Unix Shell的管道操作——把若干个命令串起来 , 前面命令的输出成为后面命令的输入 , 如此完成一个流式计算 。 (注:管道绝对是一个伟大的发明 , 他的设哲学就是KISS – 让每个功能就做一件事 , 并把这件事做到极致 , 软件或程序的拼装会变得更为简单和直观 。 这个设计理念影响非常深远 , 包括今天的Web Service , 云计算 , 以及大数据的流式计算等等)
比如 , 我们如下的shell命令:
ps auwwx | awk '{print $2}' | sort -n | xargs echo如果我们抽象成函数式的语言 , 就像下面这样:
xargs( echo, sort(n, awk('print $2', ps(auwwx))) )


推荐阅读