10分钟上手 Go 函数式编程

先来说一个简单的例子,加减乘除。

var add = func(a, b int) int { return a + b }
var multiply = func(a, b int) int { return a * b }
var result = multiply(add(1, 2), add(3, 4))
复制代码

这是一个简单的函数,用一个数学上的例子是为了后续将其更改为函数式的写法,目前来看,这个函数还简单的很,后续我会使用函数式的方式改写它。考虑到Go对函数式支持的并不是非常好,且由于确实泛型,所以容易出现恶型,也就是funcForString(string)funcForInt(int)这样。这里就简单介绍一些常见的用法。

函数是一等公民

当我们说函数式的时候,首要说明的就是,函数可以像变量一样被创建、赋值和修改。你可以将一个函数赋值给任意一个对象。举一个简单的例子:

var hello = func(name string) string { return "hello " + name }
复制代码

当你要调用它时,只需要像正常的函数一样在其后面添加()即可,如hello是一个函数变量,hello()则会执行里面的函数,也就是说:

hello        // func(name string) string { return "hello " + name }
hello("tim") // "hello tim"
复制代码

这个hello等价于

func hello(name string) string {
  return "hello " + name
}
复制代码

那为什么要这样写呢? 我们先来看一个Go语言写的http服务,其中用到了ginex框架,不需要理解内部的含义,只需要知道它创建了一个http服务端即可

server := ginex.Default()
server.Get("/api/index", func (ctx *gin.Context) {
  doSomething(ctx)
})
复制代码

正确的写法,一下子就可以明白他的简洁之处。

server.Get("/api/index", doSomething)
复制代码

纯函数

先来看一个例子,什么叫不纯的函数

var v = 20
func check(n int) bool {
  return n >= v
}
复制代码

这段代码很简单,就是传入一个参数n,然后告诉你n是否大于20。不过,由于这个v是在函数的外部,所以任何一个代码都可以对它进行修改,如果你在另外一个地方不小心修改了这个变量,那么函数的返回值可能会出现变化,这在一些情况下是不可接受的。那么正确的做法是什么呢?

func check(n int) bool {
  var v = 20
  return n >= v
}
复制代码

看到这里你可能要笑了,不就是一个变量吗,用得着这样小心吗?恰恰相反,这里只不过举了一个简单的例子,当你的代码中出现多个不纯的函数时,你会开始疑惑这个变量到底是用来做什么的?你需要小心不去更改它,而使用纯函数就没有这个问题。同时在阅读这个函数的时候也可以降低负担,不会让你思考太多外部变量,当系统大起来之后,你会回头感谢自己的规范。此外,数学上的函数本质上也是同样的输入返回同样的值,如果满足这样的特性,那么这个函数就会十分稳定。如果可以的话,最好要让代码成为一个纯函数。

柯里化

老规矩,先看一段简单的代码

var add = func(x, y int) int { return x + y }

var increment = func(x int) int { return add(1, x) }
var addTen = func(x int) int { return add(10, x) }

increment(3) // 4
addTen(5)    // 15
复制代码

在没有函数式之前,我们通常会这样构造一些函数,但引入函数式之后,我们有更好的办法,如下

var add = func(x int) func(int) int {
  return func(y int) int {
    return a + b
  }
}

var increment = add(1)
var addTen = add(10)

increment(3) // 4
addTen(5)    // 15
add(4)(5)    // 9
复制代码

可以看到这样的代码简洁了不少,去除了一堆不必要的func(int) int,转而使用add(1)这样的方式,这还提高可读性。此外,我们还可以发现一个定式:传入一个参数,返回一个函数

组合

先看看什么是组合

type F func(string) string // go1.18之前没有泛型,这里就用 string 来举例
var compose = func(f, g F) F {
  return func(x) {
    return f(g(x))
  }
}
复制代码

这段代码很好理解,就是从右往左执行函数,下面是一个实用的案例:

var toUpper = strings.ToUpper
var exclaim = func(x string) string { return x + "!" }
var shout = compose(toUpper, exclaim)

shout("hello world") // HELLO WORLD!
复制代码

通过compose函数,你可以很方便的组合各种函数,如果不使用函数式的思想,那么你的代码就是像这样的,看起来多了许多不需要的东西

var shout = function(x string) string {
  return toUpper(exclaim(x));
};
复制代码

总结

到目前为止,你应该了解如何在Go代码中运用函数式编程思想,但要注意,函数式不是万能的,很多时候你需要用到命令式编程,那么不需要太过于纠结,就用吧。

猜你喜欢

转载自juejin.im/post/6990333857102839845