搜尋
首頁後端開發Golang一文淺析Golang中的閉包

一文淺析Golang中的閉包

Nov 21, 2022 pm 08:36 PM
go閉包後端

一文淺析Golang中的閉包

1、什麼是閉包?

在真正講述閉包之前,我們先鋪墊一點知識點:

  • 函數式程式設計
  • 函數作用域
  • 作用域的繼承關係

【相關推薦:Go影片教學

1.1 前提知識鋪墊

1.2.1 函數式程式設計

函數式程式設計是一種程式設計範式,看待問題的一種方式,每一個函數都是為了用小函數組織成為更大的函數,函數的參數也是函數,函數回傳的也是函數。我們常見的程式設計範式有:

  • 命令式程式設計:
    • 主要想法為:專注於電腦執行的步驟,也就是一步一步告訴電腦先做什麼再做什麼。
    • 先把解決問題步驟規範化,抽象化為某種演算法,然後再寫具體的演算法去實現,一般只要支援過程化程式設計範式的語言,我們都可以稱為過程化程式語言,例如BASIC, C 等。
  • 聲明式程式設計:
    • 主要想法為:告訴電腦應該做什麼,但是不指定具體要怎麼做,例如 SQL,網頁程式設計的 HTML,CSS。
  • 函數式程式設計:
    • 只專注於做什麼而不關注怎麼做,有一絲宣告式程式設計的影子,但是更專注於」函數是第一位「的原則,也就是函數可以出現在任何地方,參數、變數、回傳值等等。

函數式程式設計可以認為是物件導向程式設計的對立面,一般只有一些程式語言會強調一種特定的程式設計方式,大多數的語言都是多範式語言,可以支援多種不同的程式設計方式,例如JavaScript ,Go 等。

函數式程式設計是一種思考方式,將電腦運算視為函數的計算,是一種寫程式碼的方法論,其實我應該聊函數式程式設計,然後再聊到閉包,因為閉包本身就是函數式程式設計裡面的一個特點之一。

在函數式程式設計中,函數是頭等物件,意思是說一個函數,既可以作為其它函數的輸入參數值,也可以從函數中傳回值,被修改或被指派給一個變數。 (維基百科)

一般純函數程式語言是不允許直接使用程式狀態以及可變物件的,函數式程式設計本身就是要避免使用共享狀態可變狀態,盡可能避免產生副作用

函數式程式設計一般有以下特點:

  • 函數是第一等公民:函數的地位放在第一位,可以當作參數,可以賦值,可以傳遞,可以當做回傳值。

  • 沒有副作用:函數要保持純粹獨立,不能修改外部變數的值,不修改外部狀態。

  • 引用透明:函數運行不依賴外部變數或狀態,相同的輸入參數,任何情況,所得到的回傳值都應該是一樣的。

1.2.2 函數作用域

#作用域(scope),程式設計概念,通常來說,一段程式碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的代碼範圍就是這個名字的作用域

簡單易懂的說,函數作用域是指函數可以運作的範圍。函數有點像盒子,一層套一層,作用域我們可以理解為是個封閉的盒子,也就是函數的局部變量,只能在盒子內部使用,成為獨立作用域。

一文淺析Golang中的閉包

函數內的局部變數,出了函數就跳出了作用域,找不到該變數。 (裡層函數可以使用外層函數的局部變量,因為外層函數的作用域包括了裡層函數),例如下面的innerTmep 出了函數作用域就找不到該變量,但是outerTemp 在內層函數裡面還是可以使用。

一文淺析Golang中的閉包

不管是任何語言,基本上存在一定的記憶體回收機制,也就是回收用不到的記憶體空間,回收的機制一般和上面說的函數的作用域是相關的,局部變數出了其作用域,就有可能被回收,如果還被引用著,那麼就不會被回收。

1.2.3 作用域的繼承關係

所謂作用域繼承,就是前面說的小盒子可以繼承外層大盒子的作用域,在小盒子可以直接取出大盒子的東西,但是大盒子不能取出小盒子的東西,除非發生了逃逸(逃逸可以理解為小盒子的東西給出了引用​​,大盒子拿到就可以使用)。一般而言,變數的作用域有以下兩種:

  • 全域作用域:作用於任何地方

  • ## 局部作用域:一般是程式碼區塊,函數、套件內,

    函數內部宣告/定義的變數叫做局部變數作用域僅限於函數內部

1.2 閉包的定義

「多數情況下我們並不是先理解後定義,而是先定義後理解“,先下定義,

讀不懂沒關係

閉包(closure)是

一個函數以及其捆綁的周邊環境狀態(lexical environment,詞法環境)的引用的組合。換而言之,閉包讓開發者可以從內部函數存取外部函數的作用域。閉包會隨著函數的建立而同時建立。

一句話表達:

#=函數 引用環境閉包= 函數引用環境

以上定義找不到Go語言這幾個字眼,聰明的同學一定知道,閉包是和語言無關的,不是JavaScript 特有的,也不是Go 特有的,而是函數式程式語言的獨特的,是的,你沒有看錯,任何支援函數式程式設計的語言都支援閉包,Go 和JavaScript 就是其中之二, 目前Java 目前版本也是支援閉包的,但有些人可能認為不是完美的閉包,詳細情況文中討論。

1.3 閉包的寫法

#1.3.1 初看閉包

下面是一段閉包的程式碼:

import "fmt"

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先获取函数,不求结果")
	var sum = func() int {
		fmt.Println("求结果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	return sum
}

輸出的結果:

先获取函数,不求结果
等待一会
求结果...
结果: 15

可以看出,裡面的sum() 方法可以引用外部函數lazySum() 的參數以及局部變量,在lazySum()返回函數sum() 的時候,相關的參數和變數都保存在傳回的函數中,可以之後再進行調用。

上面的函數或許還可以更進一步,體現出捆綁函數和周圍的狀態,我們加上一個次數count

import "fmt"
func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc())
	fmt.Println("结果:", sumFunc())
	fmt.Println("结果:", sumFunc())
}func lazySum(arr []int) func() int {
	fmt.Println("先获取函数,不求结果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求结果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}		return result
	}	return sum
}

上面程式碼輸出什麼呢?次數count 會不會發生變化,count明顯是外層函數的局部變量,但是在記憶體函數引用(捆綁),內層函數被暴露出去了,執行結果如下:

先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15
第 2 次求结果...
结果: 15
第 3 次求结果...
结果: 15

結果是count 其實每次都會變化,這種情況總結一下:

  • 函數體內嵌套了另外一個函數,並且傳回值是一個函數。
  • 內層函數被揭露出去,被外層函數以外的地方引用著,形成了閉包。

此時有人可能有疑問了,前面是lazySum()被創建了1 次,執行了3 次,但是如果是3 次執行都是不同的創建,會是怎麼樣呢?實驗一下:

import "fmt"
func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc())

	sumFunc1 := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc1())

	sumFunc2 := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc2())
}func lazySum(arr []int) func() int {
	fmt.Println("先获取函数,不求结果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求结果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}		return result
	}	return sum
}

執行的結果如下,每次執行都是第1 次:

先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15
先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15
先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15

從以上的執行結果可以看出:

閉套件被創建的時候,引用的外部變數count就已經被創建了1 份,也就是各自呼叫是沒有關係的

繼續拋出一個問題,**如果一個函數回傳了兩個函數,這是一個閉包還是兩個閉包呢? **下面我們實作一下:

一次傳回兩個函數,一個用來計算加和的結果,一個計算乘積:

import "fmt"
func main() {
	sumFunc, productSFunc := lazyCalculate([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc())
	fmt.Println("结果:", productSFunc())
}func lazyCalculate(arr []int) (func() int, func() int) {
	fmt.Println("先获取函数,不求结果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求加和...")
		result := 0
		for _, v := range arr {
			result = result + v
		}		return result
	}	var product = func() int {
		count++
		fmt.Println("第", count, "次求乘积...")
		result := 0
		for _, v := range arr {
			result = result * v
		}		return result
	}	return sum, product
}

運行結果如下:

先获取函数,不求结果
等待一会
第 1 次求加和...
结果: 15
第 2 次求乘积...
结果: 0

從上面結果可以看出,閉包是函數返回函數的時候,不管多少個返回值(函數),都是一次閉包,如果返回的函數有使用外部函數變量,則會綁定到一起,相互影響:

一文淺析Golang中的閉包

閉包綁定了周圍的狀態,我理解此時的函數就擁有了狀態,讓函數具有了物件所有的能力,函數具有了狀態。

1.3.2 閉包中的指標和值

上面的例子,我們閉包中用到的都是數值,如果我們傳遞指針,會是怎麼樣的呢?

import "fmt"
func main() {
	i := 0
	testFunc := test(&i)
	testFunc()
	fmt.Printf("outer i = %d\n", i)
}func test(i *int) func() {
	*i = *i + 1
	fmt.Printf("test inner i = %d\n", *i)	return func() {
		*i = *i + 1
		fmt.Printf("func inner i = %d\n", *i)
	}
}

運行結果如下:

test inner i = 1
func inner i = 2
outer i = 2

可以看出如果是指標的話,閉包裡面修改了指針對應的位址的值,也會影響閉包外面的值。這個其實很容易理解,Go 裡面沒有引用傳遞,只有值傳遞,那我們傳遞指標的時候,也是值傳遞,這裡的值是指標的數值(可以理解為位址值)。

當我們函數的參數是指標的時候,參數會拷貝一份這個指標位址,當做參數進行傳遞,因為本質還是位址,所以內部修改的時候,仍然可以對外部產生影響。

閉包裡面的數據其實位址也是一樣的,下面的實驗可以證明:

func main() {
	i := 0
	testFunc := test(&i)
	testFunc()
	fmt.Printf("outer i address %v\n", &i)
}
func test(i *int) func() {
	*i = *i + 1
	fmt.Printf("test inner i address %v\n", i)
	return func() {
		*i = *i + 1
		fmt.Printf("func inner i address %v\n", i)
	}
}

輸出如下, 因此可以推斷出,閉包如果引用外部環境的指標數據,只是會拷貝一份指標位址數據,而不是拷貝一份真正的數據(==先留個問題:拷貝的時機是什麼時候呢==):

test inner i address 0xc0003fab98
func inner i address 0xc0003fab98
outer i address 0xc0003fab98

1.3.2 閉包延遲化

上面的例子彷彿都在告訴我們,閉包創建的時候,資料就已經拷貝了,但真的是這樣麼?

下面是繼續前面的實驗:

func main() {
	i := 0
	testFunc := test(&i)
	i = i + 100
	fmt.Printf("outer i before testFunc  %d\n", i)
	testFunc()
	fmt.Printf("outer i after testFunc %d\n", i)
}func test(i *int) func() {
	*i = *i + 1
	fmt.Printf("test inner i = %d\n", *i)
		return func() {
		*i = *i + 1
		fmt.Printf("func inner i = %d\n", *i)
	}
}

我們在創建閉包之後,把數據改了,之後執行閉包,答案肯定是真實影響閉包的執行,因為它們都是指針,都是指向同一份資料:

test inner i = 1
outer i before testFunc  101
func inner i = 102
outer i after testFunc 102

假設我們換個寫法,讓閉包外部環境中的變數在宣告閉包函數的之後,進行修改:

import "fmt"

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先获取函数,不求结果")
	count := 0
	var sum = func() int {
		fmt.Println("第", count, "次求结果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	count = count + 100
	return sum
}

實際執行結果,count 會是修改後的值:

等待一会
第 100 次求结果...
结果: 15

這也證明了,實際上閉包並不會在宣告var sum = func() int {.. .}這句話之後,就將外部環境的count綁定到閉包中,而是在函數返回閉包函數的時候,才綁定的,這就是延遲綁定

如果还没看明白没关系,我们再来一个例子:

func main() {
	funcs := testFunc(100)
	for _, v := range funcs {
		v()
	}
}
func testFunc(x int) []func() {
	var funcs []func()
	values := []int{1, 2, 3}
	for _, val := range values {
		funcs = append(funcs, func() {
			fmt.Printf("testFunc val = %d\n", x+val)
		})
	}
	return funcs
}

上面的例子,我们闭包返回的是函数数组,本意我们想入每一个 val 都不一样,但是实际上 val都是一个值,==也就是执行到return funcs 的时候(或者真正执行闭包函数的时候)才绑定的 val值==(关于这一点,后面还有个Demo可以证明),此时 val的值是最后一个 3,最终输出结果都是 103:

testFunc val = 103
testFunc val = 103
testFunc val = 103

以上两个例子,都是闭包延迟绑定的问题导致,这也可以说是 feature,到这里可能不少同学还是对闭包绑定外部变量的时机有疑惑,到底是返回闭包函数的时候绑定的呢?还是真正执行闭包函数的时候才绑定的呢?

下面的例子可以有效的解答:

import (
	"fmt"
	"time"
)

func main() {
	sumFunc := lazySum([]int{1, 2, 3, 4, 5})
	fmt.Println("等待一会")
	fmt.Println("结果:", sumFunc())
	time.Sleep(time.Duration(3) * time.Second)
	fmt.Println("结果:", sumFunc())
}
func lazySum(arr []int) func() int {
	fmt.Println("先获取函数,不求结果")
	count := 0
	var sum = func() int {
		count++
		fmt.Println("第", count, "次求结果...")
		result := 0
		for _, v := range arr {
			result = result + v
		}
		return result
	}
	go func() {
		time.Sleep(time.Duration(1) * time.Second)
		count = count + 100
		fmt.Println("go func 修改后的变量 count:", count)
	}()
	return sum
}

输出结果如下:

先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15
go func 修改后的变量 count: 101
第 102 次求结果...
结果: 15

第二次执行闭包函数的时候,明显 count被里面的 go func()修改了,也就是调用的时候,才真正的获取最新的外部环境,但是在声明的时候,就会把环境预留保存下来。

其实本质上,Go Routine的匿名函数的延迟绑定就是闭包的延迟绑定,上面的例子中,go func(){}获取到的就是最新的值,而不是原始值0

总结一下上面的验证点:

  • 闭包每次返回都是一个新的实例,每个实例都有一份自己的环境。
  • 同一个实例多次执行,会使用相同的环境。
  • 闭包如果逃逸的是指针,会相互影响,因为绑定的是指针,相同指针的内容修改会相互影响。
  • 闭包并不是在声明时绑定的值,声明后只是预留了外部环境(逃逸分析),真正执行闭包函数时,会获取最新的外部环境的值(也称为延迟绑定)。
  • Go Routine的匿名函数的延迟绑定本质上就是闭包的延迟绑定。

2、闭包的好处与坏处?

2.1 好处

纯函数没有状态,而闭包则是让函数轻松拥有了状态。但是凡事都有两面性,一旦拥有状态,多次调用,可能会出现不一样的结果,就像是前面测试的 case 中一样。那么问题来了:

Q:如果不支持闭包的话,我们想要函数拥有状态,需要怎么做呢?

A: 需要使用全局变量,让所有函数共享同一份变量。

但是我们都知道全局变量有以下的一些特点(在不同的场景,优点会变成缺点):

  • 常驻于内存之中,只要程序不停会一直在内存中。
  • 污染全局,大家都可以访问,共享的同时不知道谁会改这个变量。

闭包可以一定程度优化这个问题:

  • 不需要使用全局变量,外部函数局部变量在闭包的时候会创建一份,生命周期与函数生命周期一致,闭包函数不再被引用的时候,就可以回收了。
  • 闭包暴露的局部变量,外界无法直接访问,只能通过函数操作,可以避免滥用。

除了以上的好处,像在 JavaScript 中,没有原生支持私有方法,可以靠闭包来模拟私有方法,因为闭包都有自己的词法环境。

2.2 坏处

函数拥有状态,如果处理不当,会导致闭包中的变量被误改,但这是编码者应该考虑的问题,是预期中的场景。

闭包中如果随意创建,引用被持有,则无法销毁,同时闭包内的局部变量也无法销毁,过度使用闭包会占有更多的内存,导致性能下降。一般而言,能共享一份闭包(共享闭包局部变量数据),不需要多次创建闭包函数,是比较优雅的方式。

3、闭包怎么实现的?

从上面的实验中,我们可以知道,闭包实际上就是外部环境的逃逸,跟随着闭包函数一起暴露出去。

我们用以下的程序进行分析:

import "fmt"

func testFunc(i int) func() int {
	i = i * 2
	testFunc := func() int {
		i++
		return i
	}
	i = i * 2
	return testFunc
}
func main() {
	test := testFunc(1)
	fmt.Println(test())
}

执行结果如下:

5

先看看逃逸分析,用下面的命令行可以查看:

 go build --gcflags=-m main.go

一文淺析Golang中的閉包

可以看到 变量 i被移到堆中,也就是本来是局部变量,但是发生逃逸之后,从栈里面放到堆里面,同样的 test()函数由于是闭包函数,也逃逸到堆上。

下面我们用命令行来看看汇编代码:

go tool compile -N -l -S main.go

生成代码比较长,我截取一部分:

"".testFunc STEXT size=218 args=0x8 locals=0x38 funcid=0x0 align=0x0
        0x0000 00000 (main.go:5)        TEXT    "".testFunc(SB), ABIInternal, $56-8
        0x0000 00000 (main.go:5)        CMPQ    SP, 16(R14)
        0x0004 00004 (main.go:5)        PCDATA  $0, $-2
        0x0004 00004 (main.go:5)        JLS     198
        0x000a 00010 (main.go:5)        PCDATA  $0, $-1
        0x000a 00010 (main.go:5)        SUBQ    $56, SP
        0x000e 00014 (main.go:5)        MOVQ    BP, 48(SP)
        0x0013 00019 (main.go:5)        LEAQ    48(SP), BP
        0x0018 00024 (main.go:5)        FUNCDATA        $0, gclocals·69c1753bd5f81501d95132d08af04464(SB)
        0x0018 00024 (main.go:5)        FUNCDATA        $1, gclocals·d571c0f6cf0af59df28f76498f639cf2(SB)
        0x0018 00024 (main.go:5)        FUNCDATA        $5, "".testFunc.arginfo1(SB)
        0x0018 00024 (main.go:5)        MOVQ    AX, "".i+64(SP)
        0x001d 00029 (main.go:5)        MOVQ    $0, "".~r0+16(SP)
        0x0026 00038 (main.go:5)        LEAQ    type.int(SB), AX
        0x002d 00045 (main.go:5)        PCDATA  $1, $0
        0x002d 00045 (main.go:5)        CALL    runtime.newobject(SB)
        0x0032 00050 (main.go:5)        MOVQ    AX, "".&i+40(SP)
        0x0037 00055 (main.go:5)        MOVQ    "".i+64(SP), CX
        0x003c 00060 (main.go:5)        MOVQ    CX, (AX)
        0x003f 00063 (main.go:6)        MOVQ    "".&i+40(SP), CX
        0x0044 00068 (main.go:6)        MOVQ    "".&i+40(SP), DX
        0x0049 00073 (main.go:6)        MOVQ    (DX), DX
        0x004c 00076 (main.go:6)        SHLQ    $1, DX
        0x004f 00079 (main.go:6)        MOVQ    DX, (CX)
        0x0052 00082 (main.go:7)        LEAQ    type.noalg.struct { F uintptr; "".i *int }(SB), AX
        0x0059 00089 (main.go:7)        PCDATA  $1, $1
        0x0059 00089 (main.go:7)        CALL    runtime.newobject(SB)
        0x005e 00094 (main.go:7)        MOVQ    AX, ""..autotmp_3+32(SP)
        0x0063 00099 (main.go:7)        LEAQ    "".testFunc.func1(SB), CX
        0x006a 00106 (main.go:7)        MOVQ    CX, (AX)
        0x006d 00109 (main.go:7)        MOVQ    ""..autotmp_3+32(SP), CX
        0x0072 00114 (main.go:7)        TESTB   AL, (CX)
        0x0074 00116 (main.go:7)        MOVQ    "".&i+40(SP), DX
        0x0079 00121 (main.go:7)        LEAQ    8(CX), DI
        0x007d 00125 (main.go:7)        PCDATA  $0, $-2
        0x007d 00125 (main.go:7)        CMPL    runtime.writeBarrier(SB), $0
        0x0084 00132 (main.go:7)        JEQ     136
        0x0086 00134 (main.go:7)        JMP     142
        0x0088 00136 (main.go:7)        MOVQ    DX, 8(CX)
        0x008c 00140 (main.go:7)        JMP     149
        0x008e 00142 (main.go:7)        CALL    runtime.gcWriteBarrierDX(SB)
        0x0093 00147 (main.go:7)        JMP     149
        0x0095 00149 (main.go:7)        PCDATA  $0, $-1
        0x0095 00149 (main.go:7)        MOVQ    ""..autotmp_3+32(SP), CX
        0x009a 00154 (main.go:7)        MOVQ    CX, "".testFunc+24(SP)
        0x009f 00159 (main.go:11)       MOVQ    "".&i+40(SP), CX
        0x00a4 00164 (main.go:11)       MOVQ    "".&i+40(SP), DX
        0x00a9 00169 (main.go:11)       MOVQ    (DX), DX
        0x00ac 00172 (main.go:11)       SHLQ    $1, DX
        0x00af 00175 (main.go:11)       MOVQ    DX, (CX)
        0x00b2 00178 (main.go:12)       MOVQ    "".testFunc+24(SP), AX
        0x00b7 00183 (main.go:12)       MOVQ    AX, "".~r0+16(SP)
        0x00bc 00188 (main.go:12)       MOVQ    48(SP), BP
        0x00c1 00193 (main.go:12)       ADDQ    $56, SP
        0x00c5 00197 (main.go:12)       RET
        0x00c6 00198 (main.go:12)       NOP
        0x00c6 00198 (main.go:5)        PCDATA  $1, $-1
        0x00c6 00198 (main.go:5)        PCDATA  $0, $-2
        0x00c6 00198 (main.go:5)        MOVQ    AX, 8(SP)
        0x00cb 00203 (main.go:5)        CALL    runtime.morestack_noctxt(SB)
        0x00d0 00208 (main.go:5)        MOVQ    8(SP), AX
        0x00d5 00213 (main.go:5)        PCDATA  $0, $-1
        0x00d5 00213 (main.go:5)        JMP     0

可以看到闭包函数实际上底层也是用结构体new创建出来的:

一文淺析Golang中的閉包

使用的就是堆上面的 i

一文淺析Golang中的閉包

#也就是回傳函數的時候,實際上傳回結構體,結構體裡面記錄了函數的參考環境。

4、淺聊一下

4.1 Java 位元不支援閉包?

網路上有很多種看法,但實際上Java 雖然暫時不支援返回函數作為返參,但是Java 本質上還是實現了閉包的概念的,所使用的方式是內部類的形式,因為是內部類,所以相當於自帶了一個引用環境,算是一種不完整的閉包。

目前有一定限制,例如是final 宣告的,或是明確定義的值,才可以進行傳遞:

Stack Overflow上有相關答案:stackoverflow.com/questions/5…

一文淺析Golang中的閉包

#4.2 函數式程式設計的前景如何?

以下是Wiki的內容:

函數式程式設計長期以來在學術界流行,但幾乎沒有工業應用。造成這種局面的主要原因是函數式程式設計常被認為嚴重耗費CPU和記憶體資源[18] ,這是由於在早期實作函數式程式語言時並沒有考慮過效率問題,而且面向函數式程式設計特性,如保證參考透明性等,要求獨特的資料結構和演算法。 [19]

然而,最近幾種函數式程式語言已經在商業或工業系統中使用[20],例如:

  • Erlang,它由瑞典公司愛立信在20世紀80年代後期開發,最初用於實現容錯電信系統。此後,它已在NortelFacebookÉlectricité de FranceWhatsApp等公司作為流行語言創建一系列應用程式。 [21][22]
  • Scheme,它被用作早期Apple Macintosh電腦上的幾個應用程式的基礎,並且最近已應用於諸如訓練模擬軟體和望遠鏡控制等方向。
  • OCaml,它於1990年代中期推出,已經在金融分析,驅動程式驗證,工業機器人程式設計和嵌入式軟體靜態分析等領域得到了商業應用。
  • Haskell,它雖然最初是作為一種研究語言,也已被一系列公司應用於航空航天系統,硬體設計和網路程式設計等領域。

其他在工業中使用的函數式程式語言包括多範式的Scala[23]F#,還有Wolfram語言Common LispStandard MLClojure等。

從我個人的看法,不看好純函數編程,但是函數式編程的思想,我相信以後幾乎每門高級編程需要都會具備,特別期待 Java 擁抱函數式編程。從我自己了解的語言來看,像 Go,JavaScript 中的函數式程式設計的特性,都讓開發者深愛不已(當然,如果寫出了bug,就是深惡痛疾)。

最近突然火了一波的原因,也是因為世界不停的發展,記憶也越來越大,這個因素的限制幾乎要解放了。

我相信,世界是絢麗多彩的,要是一種事物統治世界,絕無可能,更多的是百家爭鳴,程式語言或者程式設計範式也一樣,後續可能有集大成者,最終最終歷史會篩選出最終符合人類社會發展的。

更多程式相關知識,請造訪:程式設計影片! !

以上是一文淺析Golang中的閉包的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:掘金社区。如有侵權,請聯絡admin@php.cn刪除
Golang:Go編程語言解釋了Golang:Go編程語言解釋了Apr 10, 2025 am 11:18 AM

Go語言的核心特性包括垃圾回收、靜態鏈接和並發支持。 1.Go語言的並發模型通過goroutine和channel實現高效並發編程。 2.接口和多態性通過實現接口方法,使得不同類型可以統一處理。 3.基本用法展示了函數定義和調用的高效性。 4.高級用法中,切片提供了動態調整大小的強大功能。 5.常見錯誤如競態條件可以通過gotest-race檢測並解決。 6.性能優化通過sync.Pool重用對象,減少垃圾回收壓力。

Golang的目的:建立高效且可擴展的系統Golang的目的:建立高效且可擴展的系統Apr 09, 2025 pm 05:17 PM

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

SQL排序中ORDER BY語句結果為何有時看似隨機?SQL排序中ORDER BY語句結果為何有時看似隨機?Apr 02, 2025 pm 05:24 PM

關於SQL查詢結果排序的疑惑學習SQL的過程中,常常會遇到一些令人困惑的問題。最近,筆者在閱讀《MICK-SQL基礎�...

技術棧收斂是否僅僅是技術棧選型的過程?技術棧收斂是否僅僅是技術棧選型的過程?Apr 02, 2025 pm 05:21 PM

技術棧收斂與技術選型的關係在軟件開發中,技術棧的選擇和管理是一個非常關鍵的問題。最近,有讀者提出了...

如何在Go語言中使用反射對比並處理三個結構體的差異?如何在Go語言中使用反射對比並處理三個結構體的差異?Apr 02, 2025 pm 05:15 PM

Go語言中如何對比並處理三個結構體在Go語言編程中,有時需要對比兩個結構體的差異,並將這些差異應用到第�...

在Go語言中如何查看全局安裝的包?在Go語言中如何查看全局安裝的包?Apr 02, 2025 pm 05:12 PM

在Go語言中如何查看全局安裝的包?在使用Go語言開發過程中,經常會使用go...

GoLand中自定義結構體標籤不顯示怎麼辦?GoLand中自定義結構體標籤不顯示怎麼辦?Apr 02, 2025 pm 05:09 PM

GoLand中自定義結構體標籤不顯示怎麼辦?在使用GoLand進行Go語言開發時,很多開發者會遇到自定義結構體標籤在�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

Safe Exam Browser

Safe Exam Browser

Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用