Contents

函数式编程思维

Warning
本文最后更新于 April 25, 2020,文中内容可能已过时,请谨慎使用。

函数式编程是一种编程范式,常见的编程范式有 命令式编程(Imperative programming)函数式逻辑式。在函数式编程中,函数是一等公民。函数可以在任何地方定义,可以作为参数,返回值等。下面从Python 或 Golang 为例介绍一下函数式编程。


闭包

介绍一下闭包(closure)。闭包和嵌套函数类似,不同的是,这里外部函数返回的是一个函数,而不是一个具体的值。返回的函数通常也被赋予一个变量。这个变量在后面会一直被执行调用。

外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。

闭包在很多中语言中都可以使用,下面以Python 和 Golang 举例

Python 实现

举例,计算一个数的 n次冥

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def nth_power(exponent):
    def exponent_of(base):        
        return base ** exponent    
    return exponent_of

square = nth_power(2)
cube = nth_power(3)

print(square(3)) # 3 的平方
print(cube(2))   # 2 的立方

### 输出
9
8

如果我们不使用闭包,代码写出来长什么样子呢?

1
2
3
4
5
6
7
8
9
def nth_power_rewrite(base, exponent):
    return base ** exponent

print(nth_power_rewrite(3, 2)) # 3 的平方
print(nth_power_rewrite(2, 3)) # 2 的立方

### 输出
9
8

Golang 实现

举例, 斐波那契数列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func fib() func() int {
	a,b := 0,1
	return func() int {
		b = a + b
		a = b
		return b
	}
}

func main() {
	a := fib()
	for i := 0; i < 10; i++ {
		fmt.Printf("%d ", a())
	}
}

### 输出
1 2 4 8 16 32 64 128 256 512 

因为闭包的存在,我们仍然可以访问外部函数的变量对象。

装饰器

所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。最常见的使用场景有,登录状态检查,日志记录,程序执行时间记录。

如果多个函数在运行时都需要相同的检查、记录功能的话,就使用装饰器。

Python 在装饰器上有现成的使用语法,而 Golang 也可以按照其语法实现装饰器。

Python 实现

最简单的装饰器实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def my_decorator(func):
    def wrapper():
        print('wrapper of decorator')
        func()
    return wrapper

@my_decorator
def greet():
    print('hello world')

if __name__ == '__main__':
    greet()

带参数的装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def my_decorator(func):
    def wrapper(message):
        print('wrapper of decorator')
        func(message)
    return wrapper

@my_decorator
def greet(name):
    print('hello {}'.format(name))

if __name__ == '__main__':
    greet('kiosk')

带自定义参数的装饰器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def repeat(num):
    def my_decorator(func):
        def wrapper(message):
            for i in range(num):
                print('wrapper of decorator')
                func(message)
        return wrapper
    return my_decorator


@repeat(2)
def greet(name):
    print('hello {}'.format(name))


if __name__ == '__main__':
    greet('kiosk')

类装饰器

类装饰器主要依赖于函数 _call_() ,每当你调用一个类的示例时,函数**__call__()** 就会被执行一次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class Count(object):
    def __init__(self, func):
        self.func = func
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print('num of calls is: {}'.format(self.call_count))
        return self.func(*args, **kwargs)

@Count
def greet(name):
    print('hello {}'.format(name))

if __name__ == '__main__':
    greet('kiosk')
    greet('007')

实际应用

检查是否登录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import functools

def authenticate(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        request = args[0]
        if check_user_logged_in(request):  # 如果用户处于登录状态
            return func(*args, **kwargs)  # 执行函数post_comment() 
        else:
            raise Exception('Authentication failed')

    return wrapper

def check_user_logged_in(request):
    pass

@authenticate
def post_comment(request):
    pass

参数计算结果缓存

根据参数缓存每次函数调用结果,对于相同参数的,无需重新函数计算,直接返回之前缓存的返回值;比如计算 递归、动态规划 类的函数时,很多次的函数计算都是重复的。对于函数输入参数结相同的我们直接把计算结果可以缓存下来。

不带结果缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 多次调用递归会很慢
def fibonacci(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fibonacci(n-1)+fibonacci(n-2)

@log_execution_time
def run_fib():
    for i in range(1, 21):
        print(i, ":", fibonacci(i))

if __name__ == '__main__':
    run_fib()
    
###
...
19 : 4181
20 : 6765
run_fib took 5.2937360014766455 ms

带结果缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import functools

# 加上 lru_cache 缓存输入参数相同的结果,直接返回结果
@functools.lru_cache()
def fibonacci(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fibonacci(n-1)+fibonacci(n-2)

@log_execution_time
def run_fib():
    for i in range(1, 21):
        print(i, ":", fibonacci(i))

if __name__ == '__main__':
    run_fib()
    
### 
...
19 : 4181
20 : 6765
run_fib took 0.19091700232820585 ms

Golang 实现

最简单的装饰器实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import "fmt"

func decorator(callFunc func()) func()  {
	return func(){
		fmt.Println("wrapper of decorator")
		callFunc()
	}
}

func greet() {
	fmt.Printf("hello wrold ")
}

func main() {
	testFunc := decorator(greet)
	testFunc()
}

http 示例

以下完全参考 左耳朵耗子 叔的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"
)

func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->WithServerHeader()")
		w.Header().Set("Server", "HelloServer v0.0.1")
		h(w, r)
	}
}

func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->WithAuthCookie()")
		cookie := &http.Cookie{Name: "Auth", Value: "Pass", Path: "/"}
		http.SetCookie(w, cookie)
		h(w, r)
	}
}

func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->WithBasicAuth()")
		cookie, err := r.Cookie("Auth")
		if err != nil || cookie.Value != "Pass" {
			w.WriteHeader(http.StatusForbidden)
			return
		}
		h(w, r)
	}
}

func WithDebugLog(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->WithDebugLog")
		_ = r.ParseForm()
		log.Println(r.Form)
		log.Println("path", r.URL.Path)
		log.Println("scheme", r.URL.Scheme)
		log.Println(r.Form["url_long"])
		for k, v := range r.Form {
			log.Println("key:", k)
			log.Println("val:", strings.Join(v, ""))
		}
		h(w, r)
	}
}
func hello(w http.ResponseWriter, r *http.Request) {
	log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
	_, _ = fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
}

func main() {
	http.HandleFunc("/v1/hello", WithServerHeader(WithAuthCookie(hello)))
	http.HandleFunc("/v2/hello", WithServerHeader(WithBasicAuth(hello)))
	http.HandleFunc("/v3/hello", WithServerHeader(WithBasicAuth(WithDebugLog(hello))))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

多个装饰器的 Pipeline

上述的装饰器需要一个函数套一个函数,很不好看,可以写一个工具函数——用来遍历并调用各个 decorator

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc
 
func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc {
    for i := range decors {
        d := decors[len(decors)-1-i] // iterate in reverse
        h = d(h)
    }
    return h
}

### 调用
http.HandleFunc("/v4/hello", Handler(hello,
                WithServerHeader, WithBasicAuth, WithDebugLog))

泛型装饰器

和 Python 相比, Go 的装饰器没有办法做到泛型,也就是其代码耦合了需要被修饰的函数的接口类型,无法做到非常通用。

Go 语言是一个静态的语言,这意味着其类型需要在编译时就要搞定,否则无法编译。不过,Go 语言支持的最大的泛型是 interface{} 还有比较简单的 reflection 机制。可以实现泛型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
func Decorator(decoPtr, fn interface{}) (err error) {
    var decoratedFunc, targetFunc reflect.Value
 
    decoratedFunc = reflect.ValueOf(decoPtr).Elem()
    targetFunc = reflect.ValueOf(fn)
 
    v := reflect.MakeFunc(targetFunc.Type(),
            func(in []reflect.Value) (out []reflect.Value) {
                fmt.Println("before")
                out = targetFunc.Call(in)
                fmt.Println("after")
                return
            })
 
    decoratedFunc.Set(v)
    return
}

上面的代码动用了 reflect.MakeFunc() 函数制出了一个新的函数其中的 targetFunc.Call(in) 调用了被修饰的函数。 上面这个 Decorator() 需要两个参数。

  • 第一个是出参 decoPtr ,就是完成修饰后的函数
  • 第二个是入参 fn ,就是需要修饰的函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func bar(a, b string) string {
	fmt.Printf("%s, %s \n", a, b)
	return a + b
}

func main() {
	mybar := bar
	_ = Decorator(&mybar, bar)
	mybar("hello", "world!")
}

### 
before
hello, world! 
after

More Info: