先来说一个简单的例子,加减乘除。
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代码中运用函数式编程思想,但要注意,函数式不是万能的,很多时候你需要用到命令式编程,那么不需要太过于纠结,就用吧。