最近在学习函数式编程,又想顺便学下大名鼎鼎的Python ,遂把The little schemer里的scheme代码全部用Python实现了一遍。
函数式编程的精髓当然是传说中的lambda表达式,本来打算用Python lambda实现scheme代码,没想到网上一查API,却发现Python lambda实在是做的弱到不行……不能缩进(一个lambda函数只能写在一行),只能用if…else…,只能写一条语句等等。
“Why can’t lambda expressions contain statements?
Python lambda expressions cannot contain statements because Python’s syntactic framework can’t handle statements nested inside expressions. However, in Python, this is not a serious problem. Unlike lambda forms in other languages, where they add functionality, Python lambdas are only a shorthand notation if you’re too lazy to define a function.
Functions are already first class objects in Python, and can be declared in a local scope. Therefore the only advantage of using a lambda instead of a locally-defined function is that you don’t need to invent a name for the function – but that’s just a local variable to which the function object (which is exactly the same type of object that a lambda expression yields) is assigned!”
为啥lambda表达式不能包含语句?
以上是Python官方FAQ给出的答复:lambda表达式就是给你偷懒少打几个字用的,不支持在语句中嵌套表达式。
基本上Python Lambda就应用在以下场景:
1 | f = lambda x, y: x + y |
这匿名函数还tm有个名字。
或者你可以节约名字:
1 | print((lambda x, y: x + y)(1, 2)) |
是不是觉得还不如这样:
1 | print(1 + 2) |
不得不说Python lambda简直是鸡肋。
但是,你注意到Python lambda支持if…else…语句了吗,scheme也只有cond…else这一种控制语句,却是非常优美的函数式PL。
所以我试着用if…else…来搞点名堂。
1 | lambda x: True if x % 2 == 0 else False |
上面的函数判断一个数是不是偶数,其中if…else…是Python lambda的标准用法。
冒号后面接返回值return-expression,接着是该返回值的条件cond-expression,接着else,然后是else的返回值。
Python lambda总共以下2种写法,一种是不含if…else…的,一种是包含if…else…的:
1 | lambda <args>: <return-expression> |
第二条语句的**”< return-expression > if < cond-expression > else < return-expression >”** 整体也是一个return-expression。
所以我们可以在else后的return-expression中嵌套return-expression来完成复杂的条件判断语句:
1 | lambda <args>: <return-expression> if <cond-expression> else (<return-expression> if <cond-expression> else <return-expression>) |
如果想要在匿名函数中做一些除了return-expression,其他事情(比如说print()),应该怎么写?
确实不太好写,不过我还是想出了一个非常投机取巧的办法:
1 | lambda x: True if x % 2 == 0 and print(x) == None else x") |
上面这个函数不仅返回了x的值,并且还打印了x,这里我把需要执行的语句(print())转换成了逻辑表达式,而且是一个永远为真的表达式,它一定会执行,并且对业务的逻辑判断没有影响。
前提是你必须准确的知道需要执行的语句的返回值,才能写出永远为真的表达式。再写一个例子:
1 | lambda x, l: l if l.insert(0,x) == None else l |
上面这个函数把元素x插入到l的头部,并返回l。
通过这种投机取巧的办法我们可以大大扩充Python lambda的功能。
Python lambda还可以返回异常:
1 | lambda x: TypeError("x is zero.") if x == 0 else 5/x |
那么迭代(循环)怎么写?
迭代确实是一个难题,scheme这样没有迭代的语句是怎么实现迭代的?递归。
首先我们先用常规函数的递归实现迭代:
1 | #求和递归版本 |
以上2个函数均实现了list l中元素求和,功能上是等价的。
但是匿名函数要递归调用自身难度很高。
以下可能有点高能,初学者慎入。
首先要知道,lambda表达式也是一个return-expression。所以我们还可以在lambda中返回lambda:
1 | lambda <args>: lambda <args>: <return-expression> |
先看看下面这个函数
1 | def sum(f): |
这个函数接受一个自身作为参数,返回值是一个求和函数,也就是实际的递归本体,即sum(sum) = lambda l:0 if len(l) == 0 else l[0] + sum(sum)(l[1:]),如此实现递归调用自身。
我们先把sum(f)转换成lambda函数:
1 | def sum(f): |
上述2个函数式功能是等价的,返回值都相同,只是一个有名字而已,匿名函数没有名字如何调用自身?也是通过函数的参数,并且直接使用自身的定义,所以我们可以令**sum(sum)**为:
1 | sum(lambda f: lambda l:0 if len(l) == 0 else l[0] + f(f)(l[1:])) |
这就完成了匿名函数递归调用自身。测试:
1 | g = (lambda f: lambda l: 0 if len(l) == 0 else l[0] + f(f)(l[1:])) (lambda f: lambda l: 0 if len(l) == 0 else l[0] + f(f)(l[1:])) |
接下来优化一下~
注意到
1 | #f(f)(x) |
这个g就是传说中的上古神器Y-Combinator!它就是一个通用的匿名函数递归公式。
至此,我们已经把Python lambda的功能扩充的基本和普通函数差不多了(额,可能还差一点)。
而且所有函数全部一行完成,然而并没有什么卵用。