一等公民函数
函数是一等公民
Golang中函数可以:
赋值给某个变量
作为入参传递给另外一个函数
作为其他函数的返回值
在编程语言中,我们一般说这种函数是类似于变量的一等公民,因为在其他编程语言中,一般只有变量才符合这些特性。
当然,除了Golang,Javascript中函数也是一等公民。将函数做为一等公民非常有意义,这类编程语言一般可以做为函数式编程语言使用。
匿名函数
我们先来看一个将函数赋值给变量的例子。
package main
import "fmt"
func main() {
a := func() {
fmt.Println("hello world first class function")
}
a()
fmt.Printf("%T", a)
}
这里我们将一个函数赋值给了变量a
。注意这个函数没有声明函数名称,这类函数一般称为匿名函数
。
由于该匿名函数
没有函数名称,因此我们只能通过变量a
来调用。该程序执行结果如下:
hello world first class function
func()
当然我们也可以通过立即调用
来调用匿名函数。
package main
import "fmt"
func main() {
func() {
fmt.Println("hello world first class function")
}()
}
这里我们定义了一个匿名函数并进行了立即调用,直接结果如下:
hello world first class function
立即调用也可以传参数进去。
package main
import "fmt"
func main() {
func(n string) {
fmt.Println("Welcome", n)
}("Gophers")
}
该程序执行结果如下:
Welcome Gophers
自定义函数类型
我们不仅可以定义结构体类型、接口类型,还可以自定义函数类型。
type add func(a int, b int) int
这行代码定义了一个新的函数类型add
,该类型的函数要求接收两个int
参数作为入参,函数返回值类型也为int
。下面我们定义一个该类型函数的变量。
package main
import "fmt"
type add func(a int, b int) int
func main() {
var a add = func(a int, b int) int {
return a + b
}
s := a(5, 6)
fmt.Println("Sum", s)
}
这里我们定义了一个add
类型的变量a
,我们给该变量赋的值是一个入参为两个int
,返回值也为int
的函数。注意我们赋值的函数签名必须符合add
类型的定义。该程序执行结果如下:
Sum 11
高阶函数
符合下面两个条件之一的函数我们称为高阶函数:
存在一个或多个函数作为入参
将一个新的函数作为返回值
我们分别就这两种情况举个例子。
将函数作为入参传给其他函数
package main
import "fmt"
func simple(a func(a, b int) int) {
fmt.Println(a(60, 7))
}
func main() {
f := func(a, b int) int {
return a + b
}
simple(f)
}
这里我们定义了函数simple
,该函数的入参是函数类型。我们在main
函数内部创建了一个符合simple
入参要求的匿名函数,并传递给simple
函数来调用。程序输出为67
。
返回一个新函数
package main
import "fmt"
func simple() func(a, b int) int {
f := func(a, b int) int {
return a + b
}
return f
}
func main() {
s := simple()
fmt.Println(s(60, 7))
}
这里定义的函数simple
返回了一个新函数f
,f
入参为两个int
,返回值类型也是int
。我们在main
函数中调用simple()
,赋值给s
。因此s
的值即为函数f
。程序输出为67
。
闭包
将函数作为一等公民的编程语言一般都有闭包
的概念,比如Javascript,Golang也不例外。闭包
是指在函数内部可以访问到函数外部定义的变量。当然,如果函数嵌套级别较深,可能会有多层访问,也就形成了作用域链。 听起来有点晦涩,我们通过例子来理解下。
package main
import "fmt"
func appendStr() func(string) string {
t := "Hello"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}
func main() {
a := appendStr()
fmt.Println(a("World"))
fmt.Println(a("Gopher"))
}
我们定义了一个函数appendStr
,该函数的返回值是一个新函数,因此该函数是一个高阶函数。在appendStr
函数内部,我们定义了变量t
,因此在被返回的函数c
内部我们是能够访问到t
这个变量的,也就是说t
在函数c
的作用域链上。
我们在main
函数中调用appendStr()
赋值给变量a
,这时a
的值即为c
,是appendStr
返回的函数。由于t
变量在函数c
的作用域链上,因此每次调用a
(也就是调用c
)时都能访问到变量t
,也就是说可以多次调用a
来更新变量t
的值。因此第一次调用a
时t
由Hello
更新成了Hello World
,第二次调用a
时,t
的值已经时Hello World
了。
该程序输出如下:
Hello World
Hello World Gopher
这里提到的作用域链非常重要,原文没有提到这个,我个人觉得重要就补充了下,是学习高阶函数和闭包不容易理解的地方,是难点也是核心。
下面我们升级下前面的程序,来深刻体会下作用域链。
package main
import "fmt"
var g = "Hi"
func appendStr() func(string) string {
t := "Hello"
g = g + " " + "Hi"
c := func(b string) string {
t = t + " " + b + g
return t
}
return c
}
func main() {
a := appendStr()
b := appendStr()
fmt.Println(a("World"))
fmt.Println(b("Everyone"))
fmt.Println(a("Gopher"))
fmt.Println(b("!"))
}
这里我们在函数appendStr
外部定义了一个变量g
,因此g
位于函数appendStr
的作用域链上。调用appendStr
可以访问到其作用域链上的g
,第一次调用appendStr
后g
更新为Hi Hi
,第二次调用后g
更新为Hi Hi Hi
。该程序输出为:
Hello WorldHi Hi Hi
Hello EveryoneHi Hi Hi
Hello WorldHi Hi Hi GopherHi Hi Hi
Hello EveryoneHi Hi Hi !Hi Hi Hi
真实适用场景
前面我们看了几个例子,下面我们看下作为一等公民的函数都有哪些真实适用场景。 我来实现实现一个函数filter
来根据某些条件过滤一个切片,这在函数式编程中是非常常见的一种用法。
首先我们定义几个结构体类型student
。
type student struct {
firstName string
lastName string
grade string
country string
}
下面实现函数filter
,入参为一个切片列表,一个过滤条件判断函数。
func filter(s []student, f func(student) bool) []student {
var s []student
for _, v := range s {
if f(v) {
r = append(r, v)
}
}
}
下面是完成程序:
package main
import "fmt"
type student struct {
firstName string
lastName string
grade string
country string
}
func filter(s []student, f func(student) bool) []student {
var r []student
for _, v := range s {
if f(v) {
r = append(r, v)
}
}
return r
}
func main() {
s1 := student{
firstName: "Naveen",
lastName: "Ramanathan",
grade: "A",
country: "India",
}
s2 := student{
firstName: "Samuel",
lastName: "Johnson",
grade: "B",
country: "USA",
}
s := []student{s1, s2}
f := filter(s, func(s student) bool {
return s.grade == "B"
})
fmt.Println(f)
}
我们定义了两个student
结构体变量,然后通过filter
函数过滤出了grade
为B
的student。输出为:[{Samuel Johnson B USA}]
。
假设我们现在像过滤出来自India
,即印度的同学,那么只需要把过滤判断函数改为如下即可:
f := filter(s, func(s student) bool {
return s.country == "India"
})
fmt.Println(f)
是不是很方便好用?
下面我们再实现一个函数iMap
,用来遍历处理一个int
类型的切片,并返回处理后的切片。
package main
import "fmt"
func iMap(s []int, f func(int) int) []int {
var r []int
for _, v := range s {
r = append(r, f(v))
}
return r
}
func main() {
a := []int{5, 6, 7, 8, 9}
r := iMap(a, func(n int) int {
return n * 5
})
fmt.Println(r)
}
这里我们将切片中的每个元素都乘了5,输出为:[25 30 35 40 45]
。
像filter
、map
这些函数都是借助高阶函数这个特性来实现的,其实本质上是属于函数式编程的范畴,感兴趣的话可以接着去深入学习函数式编程,Haskell、Golang、Javascript、Python等编程语言都支持函数式编程。学习函数式编程对编程水平的提升绝对大有裨益。
Last updated
Was this helpful?