函数式编程

fp.png

函数式编程,是指忽略(通常是不允许)可变数据(以避免它处可改变的数据引发的边际效应),忽略程序执行状态(不允许隐式的、隐藏的、不可见的状态),通过函数作为入参,函数作为返回值的方式进行计算,通过不断的推进(迭代、递归)这种计算,从而从输入得到输出的编程范式

虽然 functional 并不易于泛型复用,但在具体类型,又或者是通过 interface 抽象后的间接泛型模型中,它是改善程序结构、外观、内涵、质量的最佳手段。
所以你会看到,在成熟的类库中,无论是标准库还是第三方库,functional 模式被广泛地采用

下面介绍几种关于使用函数式编程的编程模式

算子

functor.png

算子是一个函数空间到函数空间上的映射O:X→X, 简单说来就是进行某种“操作“,动作。与之对应的,就是被操作的对象,称之为操作数, 业务中 往往将可变部分抽象成算子,方便业务逻辑的替换

1.(x) -> x + 3

  1. func main() {
  2. op := []int{1, 2, 3, 4, 5}
  3. target := AddThree(op, func(x int) int {
  4. return x + 3
  5. })
  6. fmt.Println(target)
  7. }
  8. type Functor func(x int) int
  9. func AddThree(operand []int, fn Functor) (target []int) {
  10. for _, v := range operand {
  11. target = append(target, fn(v))
  12. }
  13. return
  14. }

应用案例:

Map-Reduce

map_filter_reduce.jpeg

这是解耦数据结构和算法最常见的方式

Map

模式:

  1. item1 --map func--> new1
  2. item2 --map func--> new2
  3. item3 --map func--> new3
  4. ...

比如 我们写一个Map函数来将所有的字符串转换成大写

  1. func TestMap(t *testing.T) {
  2. list := []string{"abc", "def", "fqp"}
  3. out := MapStrToUpper(list, func(item string) string {
  4. return strings.ToUpper(item)
  5. })
  6. fmt.Println(out)
  7. }
  8. func MapStrToUpper(data []string, fn func(string) string) []string {
  9. newData := make([]string, 0, len(data))
  10. for _, v := range data {
  11. newData = append(newData, fn(v))
  12. }
  13. return newData
  14. }

Filter

模式:

  1. item1 -- reduce func --> x
  2. item2 -- reduce func --> itme2
  3. item3 -- reduce func --> x
  1. func TestFilter(t *testing.T) {
  2. list := []string{"abc", "def", "fqp", "abc"}
  3. out := ReduceFilter(list, func(s string) bool {
  4. return strings.Contains(s, "f")
  5. })
  6. fmt.Println(out)
  7. }
  8. func ReduceFilter(data []string, fn func(string) bool) []string {
  9. newData := []string{}
  10. for _, v := range data {
  11. if fn(v) {
  12. newData = append(newData, v)
  13. }
  14. }
  15. return newData
  16. }

Reduce

模式:

  1. item1 --|
  2. item2 --|--reduce func--> new item
  3. item3 --|

比如写一个Reduce函数用于求和

  1. func TestReduce(t *testing.T) {
  2. list := []string{"abc", "def", "fqp", "abc"}
  3. // 统计字符数量
  4. out1 := ReduceSum(list, func(s string) int {
  5. return len(s)
  6. })
  7. fmt.Println(out1)
  8. // 出现过ab的字符串数量
  9. out2 := ReduceSum(list, func(s string) int {
  10. if strings.Contains(s, "ab") {
  11. return 1
  12. }
  13. return 0
  14. })
  15. fmt.Println(out2)
  16. }
  17. func ReduceSum(data []string, fn func(string) int) int {
  18. sum := 0
  19. for _, v := range data {
  20. sum += fn(v)
  21. }
  22. return sum
  23. }

应用案例: 班级统计

通过上面的一些示例,你可能有一些明白,Map/Reduce/Filter只是一种控制逻辑,真正的业务逻辑是在传给他们的数据和那个函数来定义的。是的,这也是一种将数据和控制分离经典方式

比如我们有这样一个数据集合:

  1. type Class struct {
  2. Name string // 班级名称
  3. Number uint8 // 班级编号
  4. Students []*Student // 班级学员
  5. }
  6. type Student struct {
  7. Name string // 名称
  8. Number uint16 // 学号
  9. Subjects []string // 数学 语文 英语
  10. Score []int // 88 99 77
  11. }

1.统计有多少学生数学成绩大于80

2.列出数学成绩不及格的同学

2.统计所有同学的数学平均分

修饰器

decorator.png

这种模式很容易的可以把一些函数装配到另外一些函数上,可以让你的代码更为的简单,也可以让一些“小功能型”的代码复用性更高,让代码中的函数可以像乐高玩具那样自由地拼装,

比如我们有Hello这样一个函数

  1. func main() {
  2. Hello("boy")
  3. }
  4. func Hello(s string) {
  5. fmt.Printf("hello, %s\n", s)
  6. }

需求1: 打印函数执行时间

应用案例: HTTP中间件

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6. func main() {
  7. http.ListenAndServe(":8848", http.HandlerFunc(hello))
  8. }
  9. func hello(w http.ResponseWriter, r *http.Request) {
  10. fmt.Fprintf(w, "hello, http: %s", r.URL.Path)
  11. }

需求1: 打印AccessLog

需求2: 为每个请求设置RequestID

需求3: 添加BasicAuth

优化: 多个修饰器的Pipeline

在使用上,需要对函数一层层的套起来,看上去好像不是很好看,如果需要 decorator 比较多的话,代码会比较难看了。嗯,我们可以重构一下

重构时,我们需要先写一个工具函数——用来遍历并调用各个 decorator

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

Functional Options