1. 引言
上一篇文章中,我们介绍了如何将一个并非设计用来实现面向对象思想的编程语言 — GoLang 进行封装,从而实现面向对象的特性: 通过 GoLang 实现面向对象思想
本文,我们来看看如何用 GoLang 现有的语法封装出另一个流行的编程思想 — 函数式编程。
2. 函数式编程
函数式编程思想中,程序是以函数作为单位来进行组织的,函数可以作为另一个函数的参数或返回值,通过一系列函数运算来实现最终任务的求解,函数作为语言的第一类对象,也被成为“闭包”或“仿函数”。 1980 年,伴随着 LISP 语言的诞生,lambda 表达式进入人们的视野,这就是典型的函数式编程思想的实现,一系列函数的组合运算作为计算的基础可以随时被调用。 函数式编程思想具有以下特点:
- 函数是一等公民 — 程序以函数为单位进行组织,函数可以作为另一个函数的参数、变量或返回值
- 惰性计算 — 函数被绑定到变量或参数或作为返回值返回时,并不立即执行,而是在求值程序需要产生表达式的值时进行延迟计算
- 只有“表达式”没有“语句” — 表达式和语句的区别在于表达式是一个单纯的运算,语句则是某种没有返回值的操作,这条特性是因为函数式编程诞生于数学领域,其目标是实现纯粹的运算,而在工程实践中,该特性意味着函数式编程中尽量只进行单纯的运算,而避免耦合 IO 等操作
- 无状态 — 函数只进行高度内聚的运算工作,不改变外部的系统变量
- 幂等性 — 对于同样的输入,函数总是返回同样的结果
现代编程语言中,很多语言增加了对函数式编程一些特性的支持,最为典型的就是对 lambda 表达式的支持,除 Erlang 这样纯粹的函数式编程语言,Java 这样以面向对象为核心思想的语言也在 1.8 版本的更新中引入了 lambda 表达式。
此前,主页君也曾介绍过 python 中的闭包特性: python 中的闭包
而 python 也原生支持了 lambda 表达式。 但 GoLang 为维护其语言的简洁,并没有提供对 lambda 表达式的支持,但 GoLang 中对闭包与匿名函数的支持让 GoLang 拥有了实现函数式编程思想的能力。
3. GoLang 中的匿名函数
函数式编程中,最核心的就是“一等公民” — 函数。 GoLang 对函数式编程最基本的支持就是通过匿名函数。 下面的代码展示了如何将匿名函数赋值给变量并作为函数参数传递:
代码语言:javascript复制package main
import "fmt"
func readAndCompare(cmp func(a, b int) int) int {
var a, b int
fmt.Scanln(&a, &b)
return cmp(a, b)
}
func main() {
cmp := func(a, b int) int {
if a == b {
return 0
}
if a < b {
return -1
}
return 1
}
fmt.Println(readAndCompare(cmp))
}
readAndCompare 函数从标准输入读入两个数字,并通过调用参数传入的比较函数对读取到的数字进行比较。 readAndCompare 只关心数据的读取,并不关心实际比较过程的实现,而用于比较的函数则通过参数传递从而实现惰性计算的效果。
4. 闭包
此前在 java、python 闭包相关的文章中,我们都介绍过闭包的概念,本质上,闭包就是上述函数式编程思想中的函数对象,简单的来说,闭包是函数中的函数,内部函数作为外部函数的返回值来实现函数式编程中的“惰性计算”特性。 下面是一个通过闭包实现斐波那契数列打印的例子:
代码语言:javascript复制package main
import "fmt"
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a b
return a
}
}
func main() {
fib := Fibonacci()
for i := 1; i < 10; i {
fmt.Printf("%d, ", fib())
}
fmt.Println(fib())
}
5. 总结
上面的例子中,我们可以看到,GoLang 虽然并不是一个像 Erlang 那样纯粹的函数式编程语言,但他和 python 一样提供了对函数式编程特性的支持。 可以看到,通过匿名函数与闭包的特性,GoLang 实现函数式编程十分简洁与清晰,很多情况下,对于逻辑清晰、高度内聚的运算来说,通过函数式编程的惰性运算特性,可以让代码大为简化。 但需要注意的是,如果在闭包中拥有多层函数的嵌套返回,代码的可读性与可维护性将大幅下降,所以不要试图用闭包或匿名函数来实现过于复杂或未来可能变得十分复杂的问题。 同时,函数式编程思想与面向对象编程思想最大的区别在于函数式编程只是通过将函数对象化实现延迟计算的效果,它本身并没有对问题有任何抽象,对于一个问题来说,首要考虑的应该是如何通过抽象提取过滤出问题的本质与核心,从而简化问题并提出通用性的解决方案,编写可维护的代码,是否适合使用函数式编程的思想应该在最后去考虑。