我们最好从最难的问题开始:"到底什么是函数编程 (FP)?"一个答案可能会说 FP 就是您在使用例如 Lisp、Scheme、Haskell、ML、OCAML、Clean、Mercury、Erlang(或其它一些)语言进行编程时所做的。这是一个稳妥的答案,但不能很确切地阐明问题。不幸的是,即使是函数程序员他们自己也很难对 FP 究竟是什么有个一致的认识。"盲人摸象"的故事用来形容这一情况似乎很合适。还可以放心地将 FP 与"命令编程"(使用例如 C、Pascal、C++、Java、Perl、Awk、TCL 以及其它大多数语言所执行的操作,至少是在很大程度上)进行对比。
从个人角度来说,我会将函数编程粗略地描绘为至少具有以下几个特征。称得上函数性的语言使这些事情变得简单,而使其它事情变得困难或不可能:
函数是第一类(对象)。即,可以对"数据"进行的每样操作都可以使用函数本身做到(例如将一个函数传递给另一个函数)。
函数编程的提倡者认为所有这些特征都导致更快速的开发更短以及错误更少的代码。而且,计算机科学、逻辑和数学领域的高级理论学家发现证明函数语言和程序的正式性能比命令语言和程序容易得多。
固有的 Python 函数能力
自从 Python 1.0 以来,Python 具有上面列出的大多数 FP 特征。但对于大多数 Python 特性,它们以一种非常混合的语言呈现。很大程度上是因为 Python 的 OOP 特性,您可以使用希望使用的部分而忽略其余部分(直到在稍后需要它为止)。使用 Python 2.0, 列表内涵添加了一些 非常棒的"句法上的粉饰"。虽然列表内涵没有添加什么新的能力,但它们使许多旧的能力看起来好了 许多。
Python 中 FP 的基本元素是函数 map() 、 reduce() 和 filter() ,以及运算符 lambda 。在 Python 1.x 中, apply() 函数对于将一个函数的列表返回值直接应用于另一个函数也很方便。Python 2.0 为这一目的提供了改进的语法。可能让人吃惊,但很少的这几个函数(以及基本运算符)几乎足以编写任何 Python程序;特别是,所有的流控制语句( if 、 elif 、 else 、 assert 、 try 、 except 、 finally 、 for 、 break 、 continue 、 while 、 def )可以只使用 FP 函数和运算符以函数风格处理。虽然实际上消除程序中的所有流控制命令可能只对加入"混乱的 Python"竞争(与看上去非常象 Lisp 的代码)有用,但是理解 FP 是如何使用函数和递归来表示流控制是值得的。
消除流控制语句
在我们执行消除联系时要考虑的第一件事是 Python "短路"了布尔表达式的求值这一事实。这样就提供了表达式版本的 if / elif / else 块(假设每块都调用一个函数,通常总有可能这样安排)。下面是具体方法:
清单 1. Python 中的"短路"条件调用
# Normal statement-based flow control
if
<cond1>: func1()
elif
<cond2>: func2()
else
: func3()
# Equivalent "short circuit" expression
(<cond1>
and
func1())
or
(<cond2>
and
func2())
or
(func3())
# Example "short circuit" expression
>>> x = 3
>>>
defpr
(s):
return
s
>>> (x==1
and
pr(
'one'))
or
(x==2
and
pr(
'two'))
or
(pr(
'other'))
'other'
>>> x = 2
>>> (x==1
and
pr(
'one'))
or
(x==2
and
pr(
'two'))
or
(pr(
'other'))
'two'
表达式版本的条件性调用似乎不过是个位置诀窍;不过,如果我们注意到 lambda 运算符必须返回表达式时,就更有趣了。因为 -- 如前所示 -- 表达式可以通过短路来包含条件块,所以 lambda 表达式在表达条件返回值中非常普通。在我们的示例上构建:
清单 2. Python 中 Lambda 短路
>>> pr =
lambda
s:s
>>> namenum =
lambda
x: (x==1
and
pr(
"one")) \
....
or
(x==2
and
pr(
"two")) \
....
or
(pr(
"other"))
>>> namenum(1)
'one'
>>> namenum(2)
'two'
>>> namenum(3)
'other'
函数作为第一类对象
上面的示例已经显示出函数在 Python 中所处的第一类的地位,但以很微妙的方式。在使用 lambda 操作创建 函数对象 时,我们有一些完全常规的事物。正是因为这样,我们可以将对象与名称 "pr" 和 "namenum" 绑定,使用的方法和将数字 23 或字符串 "spam" 与这些名称绑定的方法完全相同。但正如我们可以使用数字 23 而无需将它与任何名称绑定一样(换句话说,象函数自变量一样),我们可以使用用 lambda 创建的函数对象而不用将它与任何名称绑定。一个函数只是我们在 Python 中对其执行某些操作的另一个值。
我们对第一类对象所执行的主要操作是将它们传递给 FP 内置函数 map() 、 reduce() 和 filter() 。这些函数中的每一个都接受函数对象作为其第一个自变量。
map() 对指定列表中每个对应的项执行传递的函数,并返回结果列表。
reduce() 对每个后续项执行传递的函数,返回的是最终结果的内部累加;例如 reduce(lambda n,m:n*m, range(1,10)) 意味着"10 的阶乘"(换句话说,用每一项乘上前一次相乘的乘积)。
filter() 使用传递的函数对列表中的每一项"求值",然后返回经过甄别的,通过了传递函数测试的项的列表。
我们还经常将函数对象传递给自己的定制函数,但它们通常等同于上述内置函数的组合。
通过将这三种 FP 内置函数进行组合,可以执行惊人的一系列"流"操作(都不使用语句,而只使用表达式)。
Python 中的函数循环
替换循环与替换条件块一样简单。 for 可以直接转换成 map() 。对于我们的条件执行,我们需要将语句块简化成单一函数调用(我们正逐步接近通常的做法):
清单 3. Python 中的函数 'for' 循环
for
e
in
lst: func(e)
# statement-based loop
map(func,lst)
# map()-based loop
另外,对于连续程序流的函数方法有类似的技术。即,命令编程通常包含接近于"做这样,然后做那样,然后做其它事。"这样的语句。 map() 让我们正好做到这一点:
清单 4. Python 中的函数连续操作
# let's create an execution utility function
do_it =
lambda
f: f()
# let f1, f2, f3 (etc) be functions that perform actions
map(do_it, [f1,f2,f3])
# map()-based action sequence
通常,我们的整个 main 程序可以是 map() 表达式和一系列完成程序所需要执行的函数。第一类函数的另一个方便的特性就是可以将它们放在一个列表中。
while 的转换稍微复杂了一些,但仍然可以直接进行:
清单 5. Python 中的函数 'while' 循环
# statement-based while loop
while
<cond>:
<pre-suite>
if
<break_condition>:
break
else
:
<suite>
# FP-style recursive while loopp
defwhile_block
():
<pre-suite>
if
<break_condition>:
return
1
else
:
<suite>
return
0
while_FP =
lambda
: (<cond>
and
while_block())
or
while_FP()
while_FP()
while 的转换仍需要 while_block() 函数,它本身包含语句而不仅仅是表达式。但我们需要对该函数做进一步的消除(例如对模板中的 if/else 进行短路)。另外,因为循环主体(按设计)无法更改任何变量值,所以
清单 6. Python 中的函数 'echo' 循环
# imperative version of "echo()"
defecho_IMP
():
while
1:
x = raw_input(
"IMP -- ")
if
x ==
'quit':
break
else
print
x
echo_IMP()
# utility function for "identity with side-effect"
defmonadic_print
(x):
print
x
return
x
# FP version of "echo()"
echo_FP =
lambda
: monadic_print(raw_input(
"FP -- "))==
'quit'
or
echo_FP()
echo_FP()
我们所完成的是设法将涉及 I/O、循环和条件语句的小程序表示成一个带有递归的纯表达式(实际上,如果需要,可以表示成能传递到任何其它地方的函数对象)。我们 的确 仍然利用了实用程序函数 monadic_print() ,但这个函数是完全一般性的,可以在我们以后创建的每个函数程序表达式中重用(它是一次性成本)。请注意,任何包含 monadic_print(x) 的表达式所 求值 的结果都是相同的,就象它只包含 x 一样。FP(特别是 Haskell)对于"不执行任何操作,在进程中有副作用"的函数具有"单一体"意思。
消除副作用
在除去完美的、有意义的语句不用而代之以晦涩的、嵌套的表达式的工作后,一个很自然的问题是:"为什么?!"我对 FP 的所有描述都是使用 Python 做到的。但最重要的特性 -- 可能也是具体情况中最有用的特性 -- 是它消除了副作用(或者至少对一些特殊领域,例如单一体,有一些牵制作用)。绝大部分程序错误 -- 和促使程序员求助于调试来解决的问题 -- 之所以会发生,是因为在程序执行过程期间,变量包含了意外的值。函数程序只不过根本就不为变量分配值,从而避免了这一特殊问题。
让我们看一段相当普通的命令代码。它的目的是打印出乘积大于 25 的几对数字的列表。组成各对的数字本身是从另外两个列表中挑选出的。这种操作与程序员在他们程序段中实际执行的操作差不多。实现这一目的的命令方法如下:
清单 7. "打印大乘积"的命令 Python 代码
# Nested loop procedural style for finding big products
xs = (1,2,3,4)
ys = (10,15,3,22)
bigmuls = []
# ...more stuff...
for
x
in
xs:
for
y
in
ys:
# ...more stuff...
if
x*y > 25:
bigmuls.append((x,y))
# ...more stuff...
# ...more stuff...
print
bigmuls
这个项目太小,以至于没有什么可能出错。但我们的目的可能嵌在要同时实现许多其它目的的代码中。用 "more stuff" 注释的那些部分是副作用可能导致错误发生的地方。在这些地方中的任何一处,变量 xs 、 ys 、 bigmuls 、 x 、 y 有可能获得假设节略代码中的意外值。而且,在执行完这一段代码后,所有变量都可能具有稍后代码可能需要也可能不需要的一些值。很明显,可以使用函数/实例形式的封装和有关作用域的考虑来防止出现这种类型的错误。而且,您总是可以在执行完变量后 del 它们。但在实际中,这些指出类型的错误非常普遍。
目标的函数方法完全消除了这些副作用错误。以下是可能的一段代码:
清单 8. "打印大乘积"的函数 Python 代码
bigmuls =
lambda
xs,ys: filter(
lambda
(x,y):x*y > 25, combine(xs,ys))
combine =
lambda
xs,ys: map(None, xs*len(ys), dupelms(ys,len(xs)))
dupelms =
lambda
lst,n: reduce(
lambda
s,t:s+t, map(
lambda
l,n=n: [l]*n, lst))
print
bigmuls((1,2,3,4),(10,15,3,22))
在示例中,我们将匿名 ( lambda ) 函数对象与名称进行绑定,但这不是一定必要的。我们可以只嵌套定义。这样做是出于可读性目的;但也是因为 combine() 是一种随处可得的很好实用程序函数(从两个输入列表中产生所有元素对的列表)。随后的 dupelms() 主要只是帮助 combine() 发挥作用的一种方法。即使这一函数示例比命令示例更冗长,但一旦考虑到实用程序函数可以重用,那么 bigmuls() 中的新代码本身可能比命令版本中的代码数量还要少一些。
这种函数示例真正的优势在于绝对不会有变量更改其中的任何值。稍后的代码中没有 可能的不曾预料到的副作用(较早的代码中也不会有)。很明显,它本身没有副作用并不能保证代码 正确,但即使这样,这也是个优点。不过请注意,Python(与许多函数语言不同) 不能 防止名称 bigmuls 、 combine 和 dupelms 的重新绑定。如果 combine() 在程序的稍后部分中开始有其它意义,则所有努力都前功尽弃。您可以逐步建立一个 Singleton 类来包含这种类型的不可变绑定(例如 s.bigmuls 等);但本专栏并不涉及这一内容。
特别值得注意的一个问题是我们的特定目的是对 Python 2 中的新特性进行定制。最好的(也是函数的)技术既不是上面提供的命令示例,也不是函数实例,而是:
清单 9. "bigmuls" 的列表内涵 Python 代码
print
[(x,y)
for
x
in
(1,2,3,4)
for
y
in
(10,15,3,22)
if
x*y > 25]
结束语
我已介绍了使用函数等价物替换每个 Python 流控制构造所使用的方法(在过程中消除了副作用)。对特定程序进行有效转换将带来一些额外的考虑,但我们已经知道内置函数是常规而完整的。在稍后的专栏中,我们将考虑一些更高级的函数编程技术;希望能够探索函数风格的更多利弊。
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。