Python 函数式编程示例

本节基于John Hughes的论文“Why Functional Programming Matters”,来分析一个函数式编程的经典实例,这篇文章出自论文集Research Topics in Functional Programming

此论文深入分析了函数式编程,并提供了几个例子,我们只分析其中的一个:用Newton-Raphson算法求解函数(平方根函数)。

该算法的许多实现都是通过loops显式管理状态的,比如Hughes的论文中就给出了一段Fortran代码,通过有状态的命令式流程求解。

算法的主体是如何根据当前的近似值计算出下一个近似值。函数next_()sqrt(n)的当前近似值x为参数,计算出下一个近似值,并确保最终解就在之前近似值的范围内,代码如下所示。

def next_(n, x):
    return (x + n / x) / 2

该函数计算出一系列值:

Python 函数式编程示例

相近两个值的距离每次迭代减半,所以会迅速收敛到:

Python 函数式编程示例

这里没有将迭代函数命名为next(),以避免与Python的内置函数发生冲突,使用next_()保证在不冲突的前提下尽量清晰地表达出函数的功能。

在命令提示符界面使用该函数,如下所示。

>>> n = 2
>>> f = lambda x: next_(n, x)
>>> a0 = 1.0
>>> [round(x,4) for x in (a0, f(a0), f(f(a0)), f(f(f(a0))),)]
[1.0, 1.5, 1.4167, 1.4142]

首先定义收敛到 \sqrt{2} 的lambda表达式并赋值给变量f,将变量 \alpha0 作为初始值,然后对一系列递归值求值:

Python 函数式编程示例

等等。将这些函数放在一个生成器表达式中,便于对返回值做指定精度的四舍五入,从而使计算结果更易读,并便于doctest使用。该序列会快速地向 \sqrt{2} 收敛。

我们可以编写一个函数,生成一个含\alphai 的无限长序列,向平方根收敛。

def repeat(f, a):
    yield a
    for v in repeat(f, f(a)):
        yield v

该函数利用近似函数f()和初始值a生成近似值。如果把近似函数替换成前面定义的next_()函数,就可以得到关于参数n平方根的一系列近似值。

 其中repeat()函数要求f()函数只有一个参数,而定义的next_()函数有两个参数。可以用一个匿名函数对象lambda x: next_(n, x)绑定其中一个变量,创建next_()函数的部分绑定版本。
Python的生成器函数不能自动实现递归,必须显式迭代递归结果,并一个一个单独生成计算结果。使用return repeat(f, f(a))并不能多次循环生成一系列值,而会结束迭代并返回一个生成器表达式。

有两种方法可以返回一系列值,而不是生成器表达式。

  • 编写显式for循环:for x in some_iter: yield x
  • 使用yield from语句:yield from some_iter

从递归生成器表达式中返回结果,这两种方法的效果相同,这里倾向于使用yield from语句。不过在有些情况下,yield结合复杂表达式,往往比相应的映射和生成器表达式更清晰。

当然,我们并不想计算无限长序列,只要两次迭代的近似值足够接近,就可以任取其中一个作为最终解。通常用希腊字母 \epsilon 表示两个值足够接近,这里的含义是计算平方根的误差上限。

在Python中,我们需要设法从无限序列中一次取一个值,通常把复杂的递归包裹在简单的接口函数中,见如下代码片段。

def within(ε, iterable):
    def head_tail(ε, a, iterable):
        b = next(iterable)
        if abs(a-b) <= ε: return b
            return head_tail(ε, b, iterable)
    return head_tail(ε, next(iterable), iterable)

首先定义了内部函数head_tail(),以误差允许范围 \epsilon ,两个值距离足够近,表明已找到平方根的解;否则以b为参数,递归调用函数head_tail(),以获取下一次迭代的近似值。

函数within()只需要用参数iterable的第一个值初始化内部的head_tail()函数,后面由递归自动完成。

有些函数式语言允许将一个值放回可迭代序列,在Python中,这类似于用unget()或者previous()方法将一个值追加到迭代器中,然而Python的可迭代数据结构并没有提供这种高级功能。

结合上面3个函数next_()repeat()within(),即可创建求平方根函数。

def sqrt(a0, ε, n):
    return within(ε, repeat(lambda x: next_(n,x), a0))

repeat()函数基于next_(n, x)函数生成一个(可能的)无限长序列,当两次迭代值之差小于 \epsilon 时,within()即停止序列继续生成值。

使用这个sqrt()函数需要提供一个初始值a0和误差值 \epsilon,表达式sqrt(1.0, .0001, 3)表示从初始估计值1.0开始计算 \sqrt{3}
,误差值为0.0001。对于大多数应用,初始值可以选择1.0,不过初始值与实际平方根越接近,函数收敛越快。

以上近似算法的最初版本是用Miranda语言编写的,可以看到Miranda和Python的实现之间有一些显著区别,主要是Miranda可以构建cons,可以通过类似于unget的方式将值放回可迭代对象中。Miranda和Python的这种对比说明了Python适用于实现多种函数式编程技术。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程