閉包
通過一個累加器來看閉包的概念
python 閉包
def fun1(): sum = 0 def fun2(v): nonlocal sum sum += v return sum return fun2 a = fun1()for i in range(10): print(a(i))
fun1返回的不是一個值,而是一個函數 fun2,a = fun2,所以 a(i)會列印 sum 的值,為什麼 sum 一直在加呢,函數裡的值為什麼可以帶到函數體外呢,這就是閉包的神奇之處,閉包是離散數學的一個概念,可以多看看網上的講解加深印象
其實可以把閉包看做一個類, sum 就是類裡的屬性, fun2就是類的方法
所以 fun2可以使用 sum(自由變數)
java 閉包
static Function<Integer, Integer> adder() { final Holder<Integer> sum = new Holder<>(0); return (Integer value) -> { sum.value += value; return sum.value; }; }public static void main(String[] args) { Function a = adder(); for (int i = 0; i < 10; i++) { System.out.println(a.apply(i)); }}
java 裡函數不能像變數一樣傳遞,但也能類比閉包這裡的 adder 其實是一個 Function 對象
上面python
代碼裡 sum
前有個nonlocal
修飾,表明sum
不是一個局部變數,這裡直接用了 final
修飾
閉包就是能夠讀取其他函數內部變數的函數。例如在javascript中,只有函數內部的子函數才能讀取局部變數,所以閉包可以理解成“定義在一個函數內部的函數“。在本質上,閉包是將函數內部和函數外部串連起來的橋樑
go 閉包
func adder() func(int) int { sum := 0 return func(v int) int { sum += v return sum }}
但是正統的函數式編程不是這樣,函數其實是一個系統,我們只關心,入參(x)和傳回值(y)是什麼,其實裡面是怎麼實現的我們並不關心,現代的很多業務代碼,其實在函數體內做了很多事情,創造了很多變數和對象,這其實被稱為函數的'副作用'
還是看累加器
// 正統的函數式編程// 只有常量和函數type iAdder func(int) (int, iAdder)func adder2(base int) iAdder { return func(i int) (int, iAdder) { return base + i, adder2(base + i) }}func main(){ a := adder2(0) for i := 0; i < 10; i++ { var s int s, a = a(i) fmt.Println(s) }}
函數式編程入門
斐波那契數列
func fib() func() int { a, b := 1, 1 return func() int { a, b = b, a+b return a }}f := fib()for i := 0; i < 10; i++ { fmt.Println(f())}// 1 2 3 ... 55 89
這是用 print 列印的 fib 數,之前說道了 read 和 write 這兩個基本介面.
現在讓 fib這個函數實現一個 read 介面,然後任何能接收 reader 的方法都能輸出這個 fib 數了
// 定義一個函數的結構體,用函數實現介面,函數和普通變數一樣type intGen func() intfunc fib1() intGen { a, b := 1, 1 return func() int { a, b = b, a+b return a }}
把滑鼠放到intGen 上,然後右鍵
image
image
image
func (g intGen) Read(p []byte) (n int, err error) { // 下一個 fib 數 next := g() //fib 數讀不完,需要有一個結束條件 if next > 1000 { return 0, io.EOF } // 底層找一個已經實現的 s := fmt.Sprintf("%d\n", next) return strings.NewReader(s).Read(p)}
注釋已經寫得很清楚了,讓 intGen 這個函數結構體實現 reader 介面,等會就可以寫一個 接收 reader 參數的print 函數,把intGen函數當做參數傳進去了
/**列印的方法讓 fib 實現 Reader 介面,就可以用 print 方法列印了*/func printFileContents(reader io.Reader) { scanner := bufio.NewScanner(reader) for scanner.Scan() { fmt.Println(scanner.Text()) }}f1 := fib1()printFileContents(f1)// 1 2 3 5 8 13 21 34 ... 610 987
goimports
一個好用的工具
image
能夠自動整理imports
把沒用到的去除,用到的,但系統沒有的,自動 go get
但是正常是下不下來的,因為需要下載
golang.org/x/tools/cmd/goimports
,而
golang.org
在國內是被牆的
go get -v github.com/gpmgo/gopm
,github 在國內沒被牆,先下載 gopm
這個工具
- 配置
$ GOPATH:bin
gopm get -v -g -u golang.org/x/tools/cmd/goimports
用 gopm
下載Google的工具包
go install golang.org/x/tools/cmd/goimports
把 goimports
安裝到$ GOPATH 下
總結
- 函數是一等公民,可以當做參數傳給另一個函數
- 本來一個函數只能列印,但是改寫一下之後,讓他能傳一個函數進來,就可以幹任何事了,擴充性非常強(根據傳進來的函數來工作)
- 推薦一本函數式編程的書
scip
上述代碼均已上傳至 github, 歡迎 star
https://github.com/yejunyu/golearn
image