前言
- 本文是 qbit 对“lambda、闭包、函数式编程、链式调用”等概念的信息收录。
- qbit 对原理认识有限(为什么?)
- qbit 对应用很感兴趣(怎么用?)
- 本文是通俗解释(最好是一句话),不是学术化的精准描述。
- qbit 认为:用高阶函数编程就算是函数式编程。
lambda、头等函数和高阶函数
lambda 表达式
- lambda 表达式就是匿名函数
有没有听着耳熟?在学 C 语言时老师说:指针就是地址
头等函数
- 头等函数(First-class function)
- 相对于“头等函数”,qbit 更喜欢“函数是一等公民”这个说法。
- 在 JavaScript 和 Python 等语言里,函数可以像数值一样使用,比如给变量赋值、作为参数传递给其他函数,作为函数返回值等等。这时,我们就说函数是一等公民。
- 函数作为参数传值时,常写作匿名函数。
高阶函数
- 高阶函数(High-order function)是这样一种函数,它能够接受其他函数作为自己的参数,javascript/python 中数组的 map 方法,就是一个高阶函数。
- 高阶函数要求函数是一等公民。
- 高阶函数编程常常用到匿名函数。
- 高阶函数可以用来做装饰器(Decorator)。
示例
# Python3
>>> f = lambda x: x+1 # 匿名函数作为一等公民给变量赋值
>>> f(7)
8
# 匿名函数作为参数
# map 把其他函数作为自己的参数,是高阶函数
>>> list(map(lambda x: x*2, [1, 2, 3]))
[2, 4, 6]
闭包
解释
本节为宫文学《 编译原理之美》的学习笔记
- 实现闭包(Closure)的两个必要条件。
1、函数作为返回值要成为一等公民。
2、要让内层函数一直访问它环境中的变量,不管外层函数退出与否。
- 使用闭包时,从形式上来讲有 3 个点:
1、外层函数嵌套内层函数;
2、内层函数要访问外层函数的局部(本地)变量;
3、外层函数 return 内层函数。
- 闭包与面向对象类比
1、外层函数就像类(class)
2、外层函数的局部变量就像类的成员变量
3、内层函数就像类的成员函数
示例
- js 版(摘自宫文学《编译原理之美》)
/** clojure.js * 测试闭包特性 * 作者:宫文学 */
var a = 0;
var fun1 = function(){
var b = 0; // 函数内的局部变量
var inner = function(){ // 内部的一个函数
a = a+1;
b = b+1;
return b; // 返回内部的成员
}
return inner; // 返回一个函数
}
console.log("outside: a=%d", a);
var fun2 = fun1(); // 生成闭包
for (var i = 0; i< 2; i++){
console.log("fun2: b=%d a=%d",fun2(), a); // 通过fun2()来访问b
}
var fun3 = fun1(); // 生成第二个闭包
for (var i = 0; i< 2; i++){
console.log("fun3: b=%d a=%d",fun3(), a); // b等于1,重新开始
}
在 Node.js 环境下运行上面这段代码的结果如下:
outside: a=0
fun2: b=1 a=1
fun2: b=2 a=2
fun3: b=1 a=3
fun3: b=2 a=4
- Python 版(输出和原理与 js 版一致)
# Python3
a = 0
def fun1():
b = 0 # 函数内的局部变量
def inner(): # 内部的一个函数
global a # 声明全局变量
nonlocal b # 声明 inner 外最近外层函数变量
a = a + 1
b = b + 1
return b # 返回内部的成员
return inner # 返回一个函数
print("outside: a=%d" % a)
fun2 = fun1() # 生成闭包(类实例化为对象)
for i in range(0, 2):
print("fun2: b=%d a=%d" % (fun2(), a)) # 通过fun2()来访问b
fun3 = fun1() # 生成第二个闭包
for i in range(0, 2):
print("fun2: b=%d a=%d" % (fun3(), a)) # b等于1,重新开始
柯里化
解释
- 在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
- 这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege 发明的。
- 所谓"柯里化",就是把一个多参数的函数,转化为单参数函数。
- 千言不如一例,看下面的例子。
示例
- 用闭包实现柯里化
# Python3
# 普通函数
def Add(x, y):
return x + y
# 柯里化
def CurryAdd(x):
def inner(y):
return x + y
return inner
print(Add(1, 2)) # 3
print(CurryAdd(1)(2)) # 3
偏函数
解释
- 这里的偏函数特指 Python3 的 functools.partial
- 当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分(partial)参数,从而在调用时更简单。
- 偏函数生成的新函数的参数个数可能是 1 个,也可能是 2个,甚至更多。
- 柯理化是把一个有 n 个参数的函数变成 n 个只有 1 个参数的函数
示例
# Python3
from functools import partial
# 普通函数
def Add(x, y, z):
return x + y + z
# 柯里化
def CurryAdd(x):
def inner1(y):
def inner2(z):
return x + y + z
return inner2
return inner1
# 偏函数
AddBase1 = partial(Add, 1)
print(Add(1, 2, 3)) # 6(普通函数)
print(CurryAdd(1)(2)(3)) # 6(柯里化)
print(AddBase1(2, 3)) # 6(偏函数)
装饰器
解释
- 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
- 装饰器模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
示例
- 装饰器(Decorator)
# Python3
def hello(fn): # hello 是高阶函数
def wrapper():
print("hello, %s" % fn.__name__)
fn()
print("goodbye, %s" % fn.__name__)
return wrapper
@hello
def qbit():
print("i am qbit")
qbit()
输出
hello, qbit
i am qbit
goodbye, qbit
对于 Python 的这个 @注解语法糖(Syntactic sugar)来说,当你在用某个 @decorator 来修饰某个函数 func 时,如下所示:
@decorator
def func():
pass
其解释器会解释成下面这样的语句:
func = decorator(func)
函数式编程
解释
- 函数式编程(functional programming)的基础模型来源于 λ 演算。
- qbit 认为:用高阶函数编程就算是函数式编程。
- 函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。
- 函数式编程中的函数只能有一个输入,一个输出。(用柯里化将多个输入参数变成一个)
- 函数式编程三套件:Map、Reduce 和 Filter
示例
- 加法
# Python3
# 普通函数
def Add(x, y):
return x + y
# 函数式编程写法
def CurryAdd(x): # 高阶函数,闭包,柯里化
def inner(y):
return x + y
return inner
print(Add(1, 2)) # 3
print(CurryAdd(1)(2)) # 3
- 将数组中正数乘以 2 再求和
命令式编程写法
# Python3
lst = [2, -5, -2, 5, 3, 1, 0, -3]
positive_sum = 0
for i in lst:
if i > 0:
positive_sum += i * 2
print(positive_sum) # 22
函数式编程写法
# Python3
from functools import reduce
# 匿名函数作为参数
# map/reduce/filter 把其他函数作为自己的参数,是高阶函数
>>> lst = [2, -5, -2, 5, 3, 1, 0, -3]
>>> reduce(lambda x,y: x+y, map(lambda x: 2*x, filter(lambda x: x>0,lst)))
22
链式调用
解释
- 流式接口(fluent interface)是方法论,链式调用(method chaining)是实现方式
- 一般可以把链式调用等同于流式接口
- 链式调用从形式上就是一个点接一个点
- 函数式编程不一定用到链式调用,但链式调用会让函数式编程更优雅。
示例
- js 示例
// 将数组中正数乘以 2 再求和
lst = [2, -5, -2, 5, 3, 1, 0, -3]
sum = lst.filter((x) => {
return x > 0; // 筛正数
}).map((x) => {
return 2 * x; // 乘以2
}).reduce((x, y) => {
return x + y; // 求和
}, 0)
console.log(sum); // 22
- Python 实现 filter/map/reduce 链式调用
# Python3
from functools import reduce
class Chain:
def __init__(self, lst):
self.lst = lst
def map(self, fn):
return Chain(map(fn, self.lst))
def filter(self, fn):
return Chain(filter(fn, self.lst))
def reduce(self, fn):
return reduce(fn, self.lst)
# 22
print(Chain([2, -5, -2, 5, 3, 1, 0, -3])
.filter(lambda x: x>0)
.map(lambda x: 2*x)
.reduce(lambda x, y: x+y))
本文出自 qbit snap