在Go语言中.函数是唯一一种基于特定输入 实现特定任务并可反馈任务执行结果的代
码块.本质上可以说Go程序就是一组函数的集合.
1.函数特点:
1).以func关键字开头.
2).支持多返回值.
3).支持具名返回值.
4).支持递归调用.
5).支持同类型的可变参数.
6).支持defer.实现函数优雅返回.
2.一等公民概念(wiki发明人 c2站点作者的诠释):
如果一门编程语言对某种语言元素的创建和使用没有限制.我们可以像对待值(value)
一样对待这种元素语法.那么我们就称这种语法元素是这门编程语言的一等公民.拥有
一等公民待遇的语法元素可以存储在变量中.可以作为参数传递给函数.可以在函数内
部创建并可以作为返回值从函数返回.在动态类型语言中.语言运行还支持对一等公民
类型检查.
3.Go对一等公民的诠释:
3.1正常创建:
我们可以在源码底层正常创建一个函数.如下所示:
源码位置:src/fmt/print.go:newPrinter
// newPrinter allocates a new pp struct or grabs a cached one.
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
3.2在函数内创建:
在Go语言中我们可以在函数内定义一个新函数.如下在hexdumpWords函数内部定
义一个匿名函数(被赋值给变量p1).
源码位置:src/runtime/print.go:
```
// hexdumpWords prints a word-oriented hex dump of [p, end).
//
// If mark != nil, it will be called with each printed word's address
// and should return a character mark to appear just before that
// word's value. It can return 0 to indicate no mark.
func hexdumpWords(p, end uintptr, mark func(uintptr) byte) {
printlock()
var markbuf [1]byte
markbuf[0] = ' '
minhexdigits = int(unsafe.Sizeof(uintptr(0)) * 2)
for i := uintptr(0); p+i < end; i += goarch.PtrSize {
if i%16 == 0 {
if i != 0 {
println()
}
print(hex(p+i), ": ")
}
if mark != nil {
markbuf[0] = mark(p + i)
if markbuf[0] == 0 {
markbuf[0] = ' '
}
}
gwrite(markbuf[:])
val := *(*uintptr)(unsafe.Pointer(p + i))
print(hex(val))
print(" ")
// Can we symbolize val?
fn := findfunc(val)
if fn.valid() {
print("<", funcname(fn), "+", hex(val-fn.entry()), "> ")
}
}
minhexdigits = 0
println()
printunlock()
}
```
3.3作为类型:
可以使用函数自定义类型.示例如下:
源码位置:src/net/http/server.go
```
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// [Handler] that calls f.
type HandlerFunc func(ResponseWriter, *Request)
```
3.4存储到变量:
可以将定义好的函数存储到一个变量中.
源码位置:src/runtime/vdso_linux.go
3.5作为参数传入函数:
可以将函数作为参数传入函数.
源码位置:src/time/sleep.go
```
// AfterFunc waits for the duration to elapse and then calls f
// in its own goroutine. It returns a [Timer] that can
// be used to cancel the call using its Stop method.
// The returned Timer's C field is not used and will be nil.
func AfterFunc(d Duration, f func()) *Timer {
return (*Timer)(newTimer(when(d), 0, goFunc, f, nil))
}
```
3.6作为返回值从函数返回:
函数可以作为返回值从函数返回.
总结:
Go中的函数可以像普通整型那样被创建和使用.
除了上面的情况.函数还可以被放入数组 切片或map等结构.可以像其他类型变量一
样赋值个interface{}.示例如下:
```
package main
import (
"fmt"
"gomodule/data"
_ "gomodule/pubsub"
"time"
)
type binaryFunc func(int, int) int
func main() {
var i interface{} = binaryFunc(func(a, b int) int { return a + b })
c := make(chan func(int2, int3 int) int, 10)
fns := []binaryFunc{
func(a, b int) int { return a + b },
func(a, b int) int { return a - b },
func(a, b int) int { return a * b },
}
c <- func(int2, int3 int) int {
return int2 + int3
}
fmt.Println(fns[0](5, 6))
f := <-c
fmt.Println(f(5, 6))
v, ok := i.(binaryFunc)
if !ok {
fmt.Println("type assertion error")
return
}
fmt.Println(v(5, 6))
}
```
4.特殊作用:
4.1像对整型变量那样对函数进行显示类型转换.
Go是类型安全的语言.不允许隐式类型转换.
```
var a int = 5
var b int32 = 6
//违法操作.无法编译.
fmt.Println(a + b)
//可以编译
fmt.Println(a + int(b))
```
对整型变量进行的操作也可以用在函数上.函数也可以被显示类型转换.这样的类型转
换在特定的领域有奇妙的作用.示例如下:
```
package main
import (
"fmt"
"gomodule/data"
_ "gomodule/pubsub"
"net/http"
"time"
)
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(greeting))
}
func greeting(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}
```
这是一个常见的webserver例子.这里用到了一等公民函数的特性.
ListenAndServe源码如下:
```
// ListenAndServe listens on the TCP network address addr and then calls
// [Serve] with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case [DefaultServeMux] is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
```
ListenAndServe会将来自客户端的http请求交给handler处理.这里的handler参
数的类型http.handler接口如下:
```
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
```
该接口有一个serveHttp方法.原型为
*func(http.ResponseWrite,http.Request).与我们定义的方法类型一致.但是不
能直接传入.因为greeting没有实现handler接口的方法.所以无法直接传入.
因为main方法中把函数传给了ListenAndServe.看看http.HandlerFunc是什么.
```
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// [Handler] that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
```
handlerFunc是一个基于函数的新类型.然后实现了ServeHttp方法.也就是实现了
Handler接口.所以说就是把传入的函数显示的转换成了HandlerFunc类型.然后后
者实现了Handler接口.这样就满足了要求.
4.2函数式编程:
Go语言演进至今.对多种编程范式或多或少都有支持.
柯里化函数:
是把接收的多个参数函数变换成接收一个单一参数(原函数的第一个函数)的函数.并
返回接受余下的参数和返回结果的新函数技术.
```
package main
import (
"fmt"
"gomodule/data"
_ "gomodule/pubsub"
"time"
)
func times(x, y int) int {
return x + y
}
func partialTimes(x int) func(int) int {
return func(y int) int {
return times(x, y)
}
}
func main() {
timesTwo := partialTimes(2)
timesThree := partialTimes(3)
timesFour := partialTimes(3)
fmt.Println(timesTwo(5))
fmt.Println(timesThree(5))
fmt.Println(timesFour(5))
}
```
这个例子运用函数的两点性质.在函数中定义.通过返回值返回.闭包.
闭包是在函数内部定义的匿名函数.并且允许该匿名函数访问定义它的外部函数的作
用域.本质上.闭包是将函数内部和函数外部连接起来的桥梁.
函子:
函子需要满足两个条件.
函子本身是一个容器类型.以GO语言为例子.这个容器可以是切片 map 甚至
channel.
该容器类型需要实现一个方法.该方法接受一个类型参数.并在容器的每个元素上应用
这个函数.得到一个新函子.原函子容器内部元素不受影响.
```
package main
import (
"fmt"
"gomodule/data"
_ "gomodule/pubsub"
"time"
)
type IntSliceFunctor interface {
Fmap(fn func(int) int) IntSliceFunctor
}
type IntSliceFunctorImpl struct {
ints []int
}
func (isf IntSliceFunctorImpl) Fmap(fn func(int) int) IntSliceFunctor {
newInts := make([]int, len(isf.ints))
for i, elt := range isf.ints {
retInt := fn(elt)
newInts[i] = retInt
}
return IntSliceFunctorImpl{newInts}
}
func NewIntsSliceFunctor(slice []int) IntSliceFunctor {
return IntSliceFunctorImpl{slice}
}
func main() {
//原切片.
intSlice := []int{1, 2, 3, 4}
functor := NewIntsSliceFunctor(intSlice)
mapperFunc1 := func(i int) int {
return i + 10
}
fmap := functor.Fmap(mapperFunc1)
mapperFunc2 := func(i int) int {
return i + 3
}
fmap.Fmap(mapperFunc2)
fmt.Println(functor)
}
```
1).定义了一个intSliceFunctorImpl类型.用来作为函子的载体.
2).函子要实现的方法命名为Fmap.intSliceFunctorImpl类型实现了该方法.
该方法也是intSliceFunctor唯一的方法.
3).定义创建了intSliceFunctor的函数NewIntSliceFunctor.通过该函数初始化一
个切片.
4).在main方法中定义了两个转换函数.这两个函数应用到上述函子例子.得到新的函
子的内部容器元素值是元容器的元素值经过转换得到的.
5).无论如何应用转换函数.原函子容器的值不受影响.
4.2.3延续传递式:
函数式离不开递归.以求阶乘为例.
```
package main
import (
"fmt"
"gomodule/data"
_ "gomodule/pubsub"
"time"
)
func factorial(n int) int {
if n == 1 {
return 1
} else {
return n * factorial(n-1)
}
}
func main() {
fmt.Println(factorial(5))
}
```
上述例子未用到一等公民的性质.函数式编程有一种被称为延续式传递的编程风格可
以充分运用函数作为一等公民的性质.
在CPS风格中.函数是不允许有返回值.一个函数A应该将其想返回的值显示传给一个
continuation函数(一般接受一个参数).而这个函数自身是函数A的一个参数.示例如
下.
```
func CpsMax(a, b int) int {
if a > b {
return a
} else {
return b
}
}
func main() {
fmt.Println(CpsMax(5, 6))
}
```
max函数看作上面定义中的A函数在CPS转化之前的状态.根据CPS定义将其转换风
格.
1).去掉max的返回值.并为其添加一个函数类型的参数f(这个函数就是continute函
数):
```
func Max(x, y int, f func(int)) {
}
```
2).将返回结果传递给continuation函数.把return语句替换为对f函数的调用.
```
func CpsMax(x, y int, f func(int)) {
if x > y {
f(x)
} else {
f(y)
}
}
func main() {
CpsMax(5, 6, func(i int) {
fmt.Println(i)
})
}
```
总结:
Go函数可以像变量值那样赋值给变量.作为函数传递 作为返回值返回以及在函数内部
创建.
函数可以像变量那样被显示转换.
基于函数特质.了解了Go中几种有用的编程风格.
不要为了符合特定风格滥用函数特质.
星星都失重了.
如果大家喜欢我的分享的话.可以关注我的微信公众号
念何架构之路