前言

  • 本文是 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

Closure

  • 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

qbit
268 声望279 粉丝

引用和评论

0 条评论