英文原文:Charming Python: Functional programming in Python, Part 3,翻译:开源中国

摘要:  作者David Mertz在其文章《可爱的Python:“Python中的函数式编程”》中的第一部分第二部分中触及了函数式编程的大量基本概念。本文中他将继续前面的讨论,解释函数式编程的其它功能,如currying和Xoltar Toolkit中的其它一些高阶函数。

表达式绑定

有一位从不满足于解决部分问题读者,名叫Richard Davies,提出了一个问题,问是否可以将所有的绑定全部都转移到一个单个的表达式之中。首先让我们简单看看,我们为什么想这么做,然后再看看由comp.lang.python中的一位朋友提供的一种异常优雅地写表达式的方式。

让我们回想一下功能模块的绑定类。使用该类的特性,我们可以确认在一个给定的范围块内,一个特定的名字仅仅代表了一个唯一的事物。
具有重新绑定向导的 Python 函数式编程(FP)

1
2
3
4
5
6
7
8
9
10
11
12
>>> from functional import *
>>> let = Bindings()
>>> let.car = lambda lst: lst[0]
>>> let.car = lambda lst: lst[2]
Traceback (innermost last):
  File "<stdin>", line 1, in ?
  File "d:\tools\functional.py", line 976, in __setattr__
 
raise BindingError, "Binding '%s' cannot be modified." % name
functional.BindingError:  Binding 'car' cannot be modified.
>>> let.car(range(10))
0

绑定类在一个模块或者一个功能定义范围内做这些我们希望的事情,但是没有办法在一条表达式内使之工作。然而在ML家族语言(译者注:ML是一种通用的函数式编程语言),在一条表达式内创建绑定是很自然的事。
Haskell 命名绑定表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
-- car (x:xs) = -- *could* create module-level binding
list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
 
-- 'where' clause for expression-level binding
firsts1 = [car x | x <- list_of_list] where car (x:xs) = x
 
-- 'let' clause for expression-level binding
firsts2 = let car (x:xs) = x in [car x | x <- list_of_list]
 
-- more idiomatic higher-order 'map' technique
firsts3 = map car list_of_list where car (x:xs) = x
 
-- Result: firsts1 == firsts2 == firsts3 == [1,4,7]

Greg Ewing 发现用Python的list概念实现同样的效果是有可能的;甚至我们可以用几乎与Haskell语法一样干净的方式做到。
Python 2.0+ 命名绑定表达式

1
2
3
4
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
>>> [car_x for x in list_of_list for car_x in
 (x[0],)]
[1, 4, 7]

在列表解析(list comprehension)中将表达式放入一个单项元素(a single-item tuple)中的这个小技巧,并不能为使用带有表达式级绑定的高阶函数提供任何思路。要使用这样的高阶函数,还是需要使用块级(block-level)绑定,就象以下所示:
Python中的使用块级绑定的’map()’

1
2
3
4
5
>>> list_of_list = [[1,2,3],[4,5,6],[7,8,9]]
>>> let = Bindings()
>>> let.car = lambda l: l[0]
>>> map(let.car,list_of_list)
[1, 4, 7]

这样真不错,但如果我们想使用函数map(),那么其中的绑定范围可能会比我们想要的更宽一些。然而,我们可以做到的,哄骗列表解析让它替我们做名字绑定,即使其中的列表并不是我们最终想要得到的列表的情况下也没问题:
从Python的列表解析中“走下舞台”

1
2
3
4
5
6
7
8
9
10
11
12
# Compare Haskell expression:
# result = func car_car
#          where
#              car (x:xs) = x
#              car_car = car (car list_of_list)
#              func x = x + x^2
>>> [func for x in list_of_list
...      
for car in (x[0],)
...      
for func in (car+car**2,)][0]
2

我们对list_of_list列表中第一个元素的第一个元素进行了一次算数运算,而且期间还对该算术运算进行了命名(但其作用域仅仅是在表达式的范围内)。作为一种“优化”,我们可以不用费心创建多于一个元素的列表就能开始运算了,因为我们结尾处用的索引为0,所以我们仅仅选择的是第一个元素。:
从列表解析中高效地走下舞台

1
2
3
4
5
6
7
8
9
10
11
12
# Compare Haskell expression:
# result = func car_car
#          where
#              car (x:xs) = x
#              car_car = car (car list_of_list)
#              func x = x + x^2
>>> [func for x in list_of_list
...      
for car in (x[0],)
...      
for func in (car+car**2,)][0]
2

高阶函数:currying

Python内建的三个最常用的高阶函数是:map()、reduce()和filter()。这三个函数所做的事情 —— 以及谓之为“高阶”(higher-order)的原因 —— 是接受其它函数作为它们的(部分)参数。还有别的一些不属于内置的高阶函数,还会返回函数对象。
藉由函数对象在Python中具有首要地位, Python一直都有能让其使用者构造自己的高阶函数的能力。举个如下所示的小例子:
Python中一个简单函数工厂(function factory)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> def
 foo_factory():
...   
def
 foo():
...       
print
"Foo function from factory"
...   
return foo
...
>>> f = foo_factory()
>>> f()
Foo function from factory

本系列文章的第二部分我讨论过的Xoltar Toolkit中,有一组非常好用的高阶函数。Xoltar的functional模块中提供的绝大多数高阶函数都是在其它各种不同的传统型函数式编程语言中发展出来的高阶函数,其有用性已经过多年的实践验证。

可能其中最著名、最有用和最重要的高阶函数要数curry()了。函数curry()的名字取自于逻辑学家Haskell Curry,前文提及的一种编程语言也是用他姓名当中的名字部分命名的。”currying”背后隐含的意思是,(几乎)每一个函数都可以视为只带一个参数的部分函数(partial function)。要使currying能够用起来所需要做的就是让函数本身的返回值也是个函数,只不过所返回的函数“缩小了范围”或者是“更加接近完整的函数”。这和我在第二部分中提到的闭包特别相似 —— 对经过curry后的返回的后继函数进行调用时一步一步“填入”最后计算所需的更多数据(附加到一个过程(procedure)之上的数据)
现在让我们先用Haskell中一个很简单例子对curry进行讲解,然后在Python中使用functional模块重复展示一下这个简单的例子:
在Haskell计算中使用Curry

1
2
3
4
5
6
7
8
9
10
11
12
13
computation a b c d = (a + b^2+ c^3 + d^4)
check = 1 + 2^2 + 3^3 + 5^4
 
fillOne   = computation 1
-- specify "a"
fillTwo   = fillOne 2    
-- specify "b"
fillThree = fillTwo 3    
-- specify "c"
answer    = fillThree 5  
-- specify "d"
 
-- Result: check == answer == 657

现在使用Python:

在Python计算中使用Curry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> from functional import curry
>>> computation = lambda a,b,c,d: (a + b**2 + c**3 + d**4)
>>> computation(1,2,3,5)
657
>>> fillZero  = curry(computation)
>>> fillOne   = fillZero(1
# specify "a"
>>> fillTwo   = fillOne(2)  
# specify "b"
>>> fillThree = fillTwo(3)  
# specify "c"
>>> answer    = fillThree(5)
# specify "d"
>>> answer
657

第二部分中提到过的一个简单的计税程序的例子,当时用的是闭包(这次使用curry()),可以用来进一步做个对比:

Python中curry后的计税程序

1
2
3
4
5
6
7
8
9
10
11
12
from functional import *
 
taxcalc = lambda income,rate,deduct: (income-(deduct))*rate
 
taxCurry = curry(taxcalc)
taxCurry = taxCurry(50000)
taxCurry = taxCurry(0.30)
taxCurry = taxCurry(10000)
print "Curried taxes due =",taxCurry
 
print "Curried expression taxes due =", \
      curry(taxcalc)(50000)(0.30)(10000)

和使用闭包不同,我们需要以特定的顺序(从左到右)对参数进行curry处理。当要注意的是,functional模块中还包含一个rcurry()类,能够以相反的方向进行curry处理(从右到左)。
从一个层面讲,其中的第二个print语句同简单的同普通的taxcalc(50000,0.30,10000)函数调用相比只是个微小的拼写方面的变化。但从另一个不同的层面讲,它清晰地一个概念,那就是,每个函数都可以变换成仅仅带有一个参数的函数,这对于刚刚接触这个概念的人来讲,会有一种特别惊奇的感觉。

其它高阶函数

除了上述的curry功能,functional模块简直就是一个很有意思的高阶函数万能口袋。此外,无论用还是不用functional模块,编写你自己的高阶函数真的并不难。至少functional模块中的那些高阶函数为你提供了一些很值一看的思路。
它里面的其它高阶函数在很大程度上感觉有点象是“增强”版本的标准高阶函数map()、filter()和reduce()。这些函数的工作模式通常大致如此:将一个或多个函数以及一些列表作为参数接收进来,然后对这些列表参数运行它前面所接收到的函数。在这种工作模式方面,有非常大量很有意思也很有用的摆弄方法。还有一种模式是:拿到一组函数后,将这组函数的功能组合起来创建一个新函数。这种模式同样也有大量的变化形式。下面让我们看看functional模块里到底还有哪些其它的高阶函数。

sequential()和also()这两个函数都是在一系列成分函数(component function)的基础上创建一个新函数。然后这些成分函数可以通过使用相同的参数进行调用。两者的主要区别就在于,sequential()需要一个单个的函数列表作为参数,而also()接受的是一系列的多个参数。在多数情况下,对于函数的副作用而已这些会很有用,只是sequential()可以让你随意选择将哪个函数的返回值作为组合起来后的新函数的返回值。
顺序调用一系列函数(使用相同的参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> def a(x):
...     print x,
...     return "a"
...
>>> def b(x):
...     print x*2,
...     return "b"
...
>>> def c(x):
...     print x*3,
...     return "c"
...
>>> r = also(a,b,c)
>>> r
<functional.sequential instance at 0xb86ac>
>>> r(5)
5 10 15
'a'
>>> sequential([a,b,c],main=c)('x')
x xx xxx
'c'

isjoin()和conjoin()这两个函数同equential()和also()在创建新函数并对参数进行多个成分函数的调用方面非常相似。只是disjoin()函数用来查询成分函数中是否有一个函数的返回值(针对给定的参数)为真;conjoin()函数用来查询是否所有的成分函数的返回值都为真。在这些函数中只要条件允许就会使用逻辑短路,因此disjoin()函数可能不会出现某些副作用。joinfuncs()i同also()类似,但它返回的是由所有成分函数的返回值组成的一个元组(tuple),而不是选中的某个主函数。

前文所述的几个函数让你可以使用相同的参数对一系列函数进行调用,而any()、all()和 none_of()这三个让你可以使用一个参数列表对同一个函数进行多次调用。在大的结构方面,这些函数同内置的map()、reduce()和filter()有点象。 但funtional模块中的这三个高阶函数中都是对一组返回值进行布尔(boolean)运算得到其返回值的。例如:
对一系列返回值的真、假情况进行判断

1
2
3
4
5
6
7
8
9
10
11
12
>>> from functional import *
>>> isEven = lambda n: (n%2 == 0)
>>> any([1,3,5,8], isEven)
1
>>> any([1,3,5,7], isEven)
0
>>> none_of([1,3,5,7], isEven)
1
>>> all([2,4,6,8], isEven)
1
>>> all([2,4,6,7], isEven)
0

有点数学基础的人会对这个高阶函数非常感兴趣:iscompose(). 将多个函数进行合成(compostion)指的是,将一个函数的返回值同下个函数的输入“链接到一起”。对多个函数进行合成的程序员需要负责保证函数间的输入和输出是相互匹配的,不过这个条件无论是程序员在何时想使用返回值时都是需要满足的。举个简单的例子和阐明这一点:
创建合成函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def minus7(n): return n-7
...
>>> def times3(n): return n*3
...
>>> minus7(10)
3
>>> minustimes = compose(times3,minus7)
>>> minustimes(10)
9
>>> times3(minus7(10))
9
>>> timesminus = compose(minus7,times3)
>>> timesminus(10)
23
>>> minus7(times3(10))
23

后会有期

衷心希望我对高阶函数的思考能够引起读者的兴趣。无论如何,请动手试一试。试着编写一些你自己的高阶函数;一些可能很有用,很强大。告诉我它如何运行;或许这个系列之后的章节会讨论读者不断提供的新观点,新想法。

文原文:Charming Python: Functional programming in Python, Part 3,翻译:开源中国

 

可爱的 Python : Python中的函数式编程,第三部分的更多相关文章

  1. Scala 中的函数式编程基础(三)

    主要来自 Scala 语言发明人 Martin Odersky 教授的 Coursera 课程 <Functional Programming Principles in Scala>. ...

  2. Java中的函数式编程(三)lambda表达式

    写在前面 lambda表达式是一个匿名函数.在Java 8中,它和函数式接口一起,共同构建了函数式编程的框架.   lambda表达式乍看像是匿名内部类的一种语法糖,但实际上,它们是两种本质不同的事物 ...

  3. Java 中的函数式编程(Functional Programming):Lambda 初识

    Java 8 发布带来的一个主要特性就是对函数式编程的支持. 而 Lambda 表达式就是一个新的并且很重要的一个概念. 它提供了一个简单并且很简洁的编码方式. 首先从几个简单的 Lambda 表达式 ...

  4. C#中的函数式编程:序言(一)

    学了那么久的函数式编程语言,一直想写一些相关的文章.经过一段时间的考虑,我决定开这个坑. 至于为什么选择C#,在我看来,编程语言分三类:一类是难以进行函数式编程的语言,这类语言包括Java6.C语言等 ...

  5. (数据科学学习手札48)Scala中的函数式编程

    一.简介 Scala作为一门函数式编程与面向对象完美结合的语言,函数式编程部分也有其独到之处,本文就将针对Scala中关于函数式编程的一些常用基本内容进行介绍: 二.在Scala中定义函数 2.1 定 ...

  6. C#中面向对象编程中的函数式编程详解

    介绍 使用函数式编程来丰富面向对象编程的想法是陈旧的.将函数编程功能添加到面向对象的语言中会带来面向对象编程设计的好处. 一些旧的和不太老的语言,具有函数式编程和面向对象的编程: 例如,Smallta ...

  7. Apache Beam中的函数式编程理念

    不多说,直接上干货! Apache Beam中的函数式编程理念 Apache Beam的编程范式借鉴了函数式编程的概念,从工程和实现角度向命令式妥协. 编程的领域里有三大流派:函数式.命令式.逻辑式. ...

  8. C#中的函数式编程:递归与纯函数(二) 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面

    C#中的函数式编程:递归与纯函数(二)   在序言中,我们提到函数式编程的两大特征:无副作用.函数是第一公民.现在,我们先来深入第一个特征:无副作用. 无副作用是通过引用透明(Referential ...

  9. 转:JavaScript函数式编程(三)

    转:JavaScript函数式编程(三) 作者: Stark伟 这是完结篇了. 在第二篇文章里,我们介绍了 Maybe.Either.IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会 ...

随机推荐

  1. CentOS minimal版安装图形界面的步骤(自动获取IP)

    1.连接网络: CentOS minimal.iso安装好后,进入终端,默认是不开网络的, 首先启用网卡, 自动获取ip. ifconfig eth0 up   www.2cto.com  dhcli ...

  2. 定义#define

                                                    定义符号常量   当我们不止一次用到某个符号常量时,常选择对符号变量进行定义,这样就能当成一般常量来用. ...

  3. CentOS下安装Nginx并添加nginx_upload_module

    安装前,最好能保证依赖的系统软件已经升级.    yum update CentOS上安装Nginx,如果只是简单安装,不附加其他第三方模块,一句话可以搞定:    yum install nginx ...

  4. 一个简单的倒计时js插件

    接收的参数end是必须传的,格式是/分隔的日期字符串,start是可选的,不传就是从现在开始倒计时,callback也是可选的,到倒计时接收时执行自定义的函数. countdown({ 'end':' ...

  5. Zigbee、WiFi和433MHz无线技术各有特点

    Zigbee.WiFi和433MHz无线技术都属于近距离无线通讯技术,并且都使用ISM免执照频段,但它们各具特点.   ZigBee的特点是低功耗.高可靠性.强抗干扰性,布网容易,通过无线中继器可以非 ...

  6. 善待Redis里的数据--Unable to validate object

    又是一篇关于姿势的文章,为什么是”又”呢?因为上个星期刚写完一篇关于Apache Commons Pool的正确使用姿势的文章,点击此处阅读. Redis为我们提供便利的同时,我们也要善待里面的数据 ...

  7. VC++自绘界面

    // MySkinDlg.cpp : implementation file // #include "stdafx.h" #include "MySkin.h" ...

  8. 【DSA MOOC】有序向量二分查找的三个 版本

    内容来自 TsinghuaX: 30240184X 数据结构(2015秋) 课程的Vector一章,对有序向量的二分查找有三个版本 三个版本的函数原型是一致的,都是 Rank search(T con ...

  9. 【POJ 3669 Meteor Shower】简单BFS

    流星雨撞击地球(平面直角坐标第一象限),问到达安全地带的最少时间. 对于每颗流星雨i,在ti时刻撞击(xi,yi)点,同时导致(xi,yi)和上下左右相邻的点在ti以后的时刻(包括t)不能再经过(被封 ...

  10. python内置函数(2)-递归与迭代

    这篇文章简单介绍了迭代和递归的概念.两者的区别 什么是迭代: 迭代是重复反馈过程的活动,其目的通常是为了接近并达到所需的目标或结果.每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来 ...