《Go语言精进之路》读书笔记 | 让自己习惯于函数是“一等公民”
Posted COCOgsta
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Go语言精进之路》读书笔记 | 让自己习惯于函数是“一等公民”相关的知识,希望对你有一定的参考价值。
书籍来源:《Go语言精进之路:从新手到高手的编程思想、方法和技巧》
一边学习一边整理读书笔记,并与大家分享,侵权即删,谢谢支持!
附上汇总贴:《Go语言精进之路》读书笔记 | 汇总_COCOgsta的博客-CSDN博客
Go程序就是一组函数的集合。函数在Go语言中属于“一等公民”。
21.1 什么是“一等公民”
引用一下wiki发明人、C2站点作者Ward Cunningham对“一等公民”的诠释:
如果一门编程语言对某种语言元素的创建和使用没有限制,我们可以像对待值(value)一样对待这种语法元素,那么我们就称这种语法元素是这门编程语言的“一等公民”。拥有“一等公民”待遇的语法元素可以存储在变量中,可以作为参数传递给函数,可以在函数内部创建并可以作为返回值从函数返回。
基于上面关于“一等公民”的诠释,我们来看看Go语言的函数是如何满足上述条件而成为“一等公民”的。
(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)在函数内创建
可以在函数内定义一个新函数,如下面代码中在hexdumpWords函数内部定义的匿名函数(被赋值给变量p1)。
// $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)作为类型
可以使用函数来自定义类型,如下面代码中的HandlerFunc、visitFunc和action:
// $GOROOT/src/net/http/server.go
type HandlerFunc func(ResponseWriter, *Request)
// $GOROOT/src/sort/genzfunc.go
type visitFunc func(ast.Node) ast.Visitor
// codewalk: https://tip.golang.org/doc/codewalk/functions/
type action func(current score) (result score, turnIsOver bool)
复制代码
(4)存储到变量中
可以将定义好的函数存储到一个变量中,如下面代码中的apply:
// $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)作为参数传入函数
可以将函数作为参数传入函数,比如下面代码中函数AfterFunc的参数f:
// $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)作为返回值从函数返回
函数还可以被作为返回值从函数返回,如下面代码中函数makeCutsetFunc的返回值就是一个函数:
// $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
复制代码
正如Ward Cunningham对“一等公民”的诠释,Go中的函数可以像普通整型值那样被创建和使用。
21.2 函数作为“一等公民”的特殊运用
- 像对整型变量那样对函数进行显式类型转换
对整型变量进行的操作也可以用在函数上,即函数也可以被显式类型转换,并且这样的类型转换在特定的领域具有奇妙的作用。
// chapter4/sources/function_as_first_class_citizen_2.go
func greeting(w http.ResponseWriter, r *http.Request)
fmt.Fprintf(w, "Welcome, Gopher!\\n")
func main()
http.ListenAndServe(":8080", http.HandlerFunc(greeting))
复制代码
上述代码是最为常见的一个用Go构建的Web Server的例子。其工作机制很简单,当用户通过浏览器或类似curl这样的命令行工具访问Web Server的8080端口时,会收到“Welcome, Gopher!”这行文字版应答。
- 函数式编程
虽然Go不推崇函数式编程,但有些时候局部应用函数式编程风格可以写出更优雅、更简洁、更易维护的代码。
(1)柯里化函数
柯里化是把接受多个参数的函数变换成接受一个单一参数(原函数的第一个参数)的函数,并返回接受余下的参数和返回结果的新函数的技术。
来看一个用Go编写的柯里化函数的例子:
// chapter4/sources/function_as_first_class_citizen_4.go
package main
import "fmt"
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(4)
fmt.Println(timesTwo(5))
fmt.Println(timesThree(5))
fmt.Println(timesFour(5))
复制代码
运行这个例子:
$ go run function_as_first_class_citizen_4.go
10
15
20
复制代码
这里的柯里化是指将原来接受两个参数的函数times转换为接受一个参数的函数partialTimes的过程。通过partialTimes函数构造的timesTwo将输入参数扩大为原先的2倍、timesThree将输入参数扩大为原先的3倍,以此类堆。
这个例子利用了函数的两点性质:在函数中定义,通过返回值返回;闭包。
闭包是在函数内部定义的匿名函数,并且允许该匿名函数访问定义它的外部函数的作用域。
上述示例,partialTimes内部定义的匿名函数就是一个闭包,该匿名函数访问了其外部函数partialTimes的变量x。这样当调用partialTimes(2)时,partialTimes实际上返回一个调用times(2, y)的函数:
timesTwo = func(y int) int
return times(2, y)
复制代码
(2)函子
什么是函子呢?具体来说,函子需要满足两个条件:
函子本身是一个容器类型,以Go语言为例,这个容器可以是切片、map甚至channel; 该容器类型需要实现一个方法,该方法接受一个函数类型参数,并在容器的每个元素上应用那个函数,得到一个新函子,原函子容器内部的元素值不受影响。
函子非常适合用来对容器集合元素进行批量同构处理,而且代码也比每次都对容器中的元素进行循环处理要优雅、简洁许多。但要想在Go中发挥函子的最大效能,还需要Go对泛型提供支持,否则我们就需要为每一种容器类型都实现一套对应的Functor机制。
(3)延续传递式
函数式编程有一种被称为延续传递式(Continuation-passing Style,CPS)的编程风格可以充分运用函数作为“一等公民”的特质。
在CPS风格中,函数是不允许有返回值的。一个函数A应该将其想返回的值显式传给一个continuation函数(一般接受一个参数),而这个continuation函数自身是函数A的一个参数。
这种CPS风格虽然利用了函数作为“一等公民”的特质,但是其代码理解起来颇为困难,这种风格真的好吗?选择了不适合的风格或者为了函数式而进行函数式编程,那么就会出现代码难于理解且代码执行效率不高的情况。
以上是关于《Go语言精进之路》读书笔记 | 让自己习惯于函数是“一等公民”的主要内容,如果未能解决你的问题,请参考以下文章
《Go语言精进之路》读书笔记 | 在init函数中检查包级变量的初始状态
《Go语言精进之路》读书笔记 | 使用Go语言原生编码思维来写Go代码