IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux  >

可爱的 Python: 在 Python 中进行函数编程,第 3 部分

Curry 和其它的高阶函数

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz, Ph.D. (mertz@gnosis.cx), 应用精神疗法专家, Gnosis Software, Inc.

2001 年 6 月 01 日

作者 David Mertz 在早期的 可爱的 Python文章:“在 Python 中进行函数编程”的 第 1 部分第 2 部分中介绍过许多函数编程的基本概念。这里通过列举更多的性能,象包含在 Xoltar 工具包中的 Curry 和其它的高阶函数,来继续进行讨论。

表达式绑定

一个永不满足于部分解决方案的读者 -- Richard Davies -- 提出了我们是否能够始终把绑定移到单独的表达式中这样一个问题。让我们快速地浏览一下我们为什么想要这么做,并展示一个由 comp.lang.python 的投稿者提供的一个非常棒的方法。

让我们首先回顾一下 functional 模块中的 Bindings 类。通过使用那个类的属性,我们能够确保一个特殊名称在给定块的范围内只有一个意思:


清单 1:带有受保护重绑定的 Python FP 部分
>>> 
        
        
          
          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
      
      

Bindings 类在一个模块或者函数 def 的范围之内完成我们想要的功能,但是在一个单独的表达式内无法使其工作。然而,在 ML-系列语言中,就能很自然地在单个表达式内创建绑定:


清单 2:Haskell 表达式级别的名称绑定
        
        -- car (x:xs) = x -- *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 的列表理解可以达到同样的效果;我们甚至可以用象 Haskell 的语法那样清楚的方法来做:


清单 3:Python 2.0+ 表达式级别名称绑定
>>> 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]
      
      

有个诀窍是把表达式放在列表理解的单项元组里,但是并不能提供任何使用表达层次绑定来处理高阶函数的方法。为了使用高阶函数,我们仍然需要使用块层次的绑定,如下所示:


清单 4:带有 'map()' 的 Python 块级别绑定
>>> 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() ,绑定的范围仍旧比我们期望的要扩大了一点。虽然如此,即便在列表不是我们最终想使用的情况下,耐心地使用列表理解来为我们做名字绑定也是可能的:


清单 5:Python 列表理解的“逐级下降”
        
        # 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 来选择唯一的第一个元素:


清单 6:列表理解的有效逐级下降
>>> [func for x in list_of_list[:1]
... for car in (x[0],)
... for func in (car+car**2,)][0]
2





回页首


高阶函数:Curry

有三个最普通的高阶函数内建在 Python 中: map()reduce()filter() 。这些函数所做的 -- 也是我们称之为“高阶”的原因 -- 是把其他的函数作为他们的(一些)参数。其它的高阶函数(不是这些内建函数)返回函数对象。

Python 总是通过第一类函数对象的地位优势赋予使用者建造他们自己的高阶函数的能力。以下是一个小示例:


清单 7:小 Python 函数工厂
>>> 
        
        
          
          def
        
         
        
        
          
          foo_factory
        
        ():
... 
        
        
          
          def
        
         
        
        
          
          foo
        
        ():
... 
        
        
          
          print
        
         "Foo function from factory"
... 
        
        
          
          return
        
         foo
...
>>> f = foo_factory()
>>> f()
Foo function from factory
      
      

我在这个系列的 第 2 部分讨论过 Xoltar 工具包,它是带有高阶函数的非常好的集合。Xoltar 的 functional 模块提供的大多数函数是从各种传统的函数语言发展而来的,并且它们的有效性在这么多年已经被证实了。

可能最著名的和最重要的高阶的函数是 curry()curry() 是以逻辑学家 Haskell Curry 命名的,他的名字也曾命名上面提到的编程语言。"currying" 优越的地方在于它能够象对待一个局部参数函数那样处理(几乎)每一个函数。所有对于 Curry 有必要做的就是容许函数的返回值对于它们自己也是函数,但是带有返回的函数可能是“狭窄”的或者“接近完成”的。这项工作和我所写的 第 2 部分有些相近 -- 每次连续的调用 curried 返回函数“充满”了在最后计算中的更多数据(数据依附于过程)。

让我们用 Haskell 的一个非常简单的示例来说明 Curry,然后在使用 functional 模块,在 Python 中重复使用同样的示例:


清单 8:Curry 一个 Haskell 计算
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 中:


清单 9:Curry 一个 Python 计算
>>> 
        
        
          
          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
      
      

通过 第 2 部分(这次使用 curry() )中的税收计算程序的示例,可以进一步地和闭包进行比较:


清单 10:Python Curry 过的税收计算
				
        
        
          
          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)
      
      

不像闭包,我们需要用一个特定的顺序(从左向右)来排列参数。但是要注意到 functional 也包含一个开始于另一端(从右向左)的 curry() 类。

在同一级别示例的第二个 print 声明与直接调用普通的 taxcalc(50000,0.30,10000) 仅在拼写上做了少许的改变。然而,在不同的级别上,它使得每一个函数可以只有一个参数的函数这一概念更加清楚 -- 刚一接触这种思想的人可能会感到很惊讶。





回页首


混合高阶函数

在 curring 的“基本”操作之外, functional 提供了一个有趣的高阶函数的掠夺包。而且,写自己的高阶函数(带或者不带 functional ) 的确不难。至少带有 functional 的高阶函数会提供一些很好的主意。

对于大部分人来说,高阶函数感觉起来就像是标准的 map()filter()reduce() 的“增强”版本。通常,这些函数的模式是概略地“把一个或多个函数和一些列表作为参数,然后应用这些函数列出参数。”关于这个题目有数量惊人的有趣和有用的方法。另一个模式是“获取函数集合,并创建一个合并这些函数功能的函数。”再次,可能有众多的变化,让我们看一下 functional 提供的东西。函数 sequential()also() 都能够创建一个基于组件函数序列的函数。组件函数可以用相同的参数来调用。两者主要的不同之处在于 sequential() 期望一个单一列表作为参数,而 also() 使用一列参数。在多数情况下,这些对于函数方面的影响是很有用的,但是 sequential() 随意地让您挑选哪个函数提供组合返回值:


清单 11:连续的函数调用(用相同的参数)
>>> 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'

在创建新功能(这些功能将参数应用于几个组件函数)方面,函数 disjoin()conjoin()sequential()also() 相似。但是 disjoin() 询问是不是 任何组成函数都返回真(给定了参数),而且 conjoin() 询问是不是 所有的组件函数都返回真。在可能的地方应用逻辑快捷键,那么 disjoin() 可能就不会发生某些方面的影响。 joinfuncs()also() 类似,它返回一个组件返回值的元组而不是选择一个主要返回值。

先前的函数让您访问具有相同参数的多个函数, any() all()none_of() 让您根据参数列表调用相同的函数。在通常的结构中,它们有一点象内建的 map()reduce()filter() 函数。但是这些来自于 functional 的特殊高阶函数询问的是关于返回值集合的布尔问题。例如:


清单 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

对于那些有一点数学背景人来说,特别有意思的高阶函数是 compose() 。一些函数的组合是一个函数返回值的“联合链”,并输入下一个函数。编写这些函数的程序员应该负责确保输出和输入相匹配,但是这样的话,任何时候程序员都使用一个返回值。一个简单的示例就会让您明白:


清单 13: 创建组合函数
>>> 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





回页首


直到下一次

我希望对高阶函数的最新研究能够唤起读者在一定的思考方式上的兴趣。请尽一切办法使用它。试着创建一些自己的高阶函数;有些可能是非常有用和非常强大的。请让我知道它是如何运行的;可能这个特别系列的后面部分将会讨论读者们继续提供的新奇的、迷人的方法。



参考资料



关于作者

因为没有直觉的概念是空洞的,没有概念的直觉是盲目的,David Mertz 希望在他的办公室里放置弥尔顿的石膏像。现在他开始计划他的生日。可以通过 mertz@gnosis.cx 与 David 联系;他的一生完全投入在 http://gnosis.cx/publish/ 上。非常欢迎对过去的、这一篇或将来的专栏文章提出意见和建议。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款