Loading

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需要语言支持尾递归优化,避免代码执行效率不高的问题

posted @ 2024-02-13 10:56  brynchen  阅读(6)  评论(0编辑  收藏  举报