Go语言精进之路读书笔记第21条——让自己习惯于函数是"一等公民"
21.1 什么是"一等公民"
(1) 正常创建
// $GOROOT/src/fmt/print.go
func newPrinter() *pp {
p := ppFree.Get().(*pp)
p.panicking = false
p.erroring = false
p.wrapErrs = false
p.fmt.init(&p.buf)
return p
}
(2) 在函数内创建,定义匿名函数赋值给变量
// $GOROOT/src/runtime/print.go
func hexdumpWords(p, end uintptr, mark func(uintptr) byte) {
p1 := func(x uintptr) {
var buf [2 * sys.PtrSize]byte
for i := len(buf) - 1; i >= 0; i-- {
if x&0xF < 10 {
buf[i] = byte(x&0xF) + '0'
} else {
buf[i] = byte(x&0xF) - 10 + 'a'
}
x >>= 4
}
gwrite(buf[:])
}
...
}
(3) 作为类型
// $GOROOT/src/net/http/server.go
type HandlerFunc func(ResponseWrite, *Request)
(4) 存储到变量中,可以将定义好的函数存储到一个变量中
// $GOROOT/src/runtime/vdso_linux.go
func vdsoParseSymbols(info *vdsoInfo, version int32) {
...
apply := func(symIndex uint32, k vdsoSymbolKey) bool {
sym := &info.symtab[symIndex]
typ := _ELF_ST_TYPE(sym.st_info)
bind := _ELF_ST_BIND(sym.st_info)
...
*k.ptr = info.loadOffset + uintptr(sym.st_value)
return true
}
...
}
(5) 作为参数传入函数
// $GOROOT/src/time/sleep.go
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}
(6) 作为返回值从函数返回
// $GOROOT/src/strings/strings.go
func makeCutsetFunc(cutset string) func(rune) bool {
if len(cutset) == 1 && cutset[0] < utf8.RuneSelf {
return func(r rune) bool {
return r == rune(cutset[0])
}
}
if as, isASCII := makeASCIISet(cutset); isASCII {
return func(r rune) bool {
return r < utf8.RuneSelf && as.contains(byte(r))
}
}
return func(r rune) bool { return IndexRune(cutset, r) >= 0 }
}
(7) 其他
除了上面那些例子,函数还可以被放入数组、切片或map等结构中,可以被赋值给interface{},可以建立元素为函数的channel
21.2 函数作为"一等公民"的特殊运用
1.像对整型变量那样对函数进行显式类型转换
func greeting(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome, Gopher!\n")
}
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(greeting))
//http.ListenAndServe(":8080", greeting)
}
// $GOROOT/src/net/http/server.go
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
...
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
...
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP调用f(w, r)
func (f HandlerFunc) ServeHttp(w ResponseWriter, r *Request) {
f(w, r)
}
之所以http.HandlerFunc(greeting)
可以通过编译器检查,是因为HandlerFunc的底层类型是func(ResponseWriter, *Request),与greeting的原型是一致的
2.函数式编程
(1) 柯里化函数
函数柯里化(currying)是把接受多个参数的函数变换成接受一个单一参数(原函数的第一个参数)的函数,并返回接受余下的参数和返回结果的新函数的技术。
闭包是在函数内部定义的匿名函数,并且允许该匿名函数访问定义它的外部函数的作用域
(2) 函子(functor)
函子需要满足两个条件:
- 函子本身是一个容器类型,以Go语言为例,这个容器可以是切片、map甚至channel;
- 该容器类型需要实现一个方法,该方法接受一个函数类型参数,并在容器的每个元素上运用那个函数,得到一个新函子,原函子容器内部的元素值不受影响
函子非常适合用来对容器集合元素进行批量同构处理,而且代码也比每次都对容器中的元素进行循环处理要优雅、简洁需要多。
还需要Go对泛型提供支持,否则就要为每一种容器类型都实现一套对应的Functor机制。
(3) 延续传递式(Continuation-passing Style,CPS)
在CPS风格中,函数是不允许有返回值的。一个函数A应该将其想返回的值显式传给一个continuation函数(一般接受一个参数),而这个continuation函数自身是函数A的一个参数。
CPS需要语言支持尾递归优化,避免代码执行效率不高的问题