ホームページ >バックエンド開発 >Golang >Golang におけるクロージャの簡単な分析

Golang におけるクロージャの簡単な分析

青灯夜游
青灯夜游転載
2022-11-21 20:36:036576ブラウズ

Golang におけるクロージャの簡単な分析

1. クロージャとは何ですか?

実際にクロージャについて話す前に、いくつかの基礎を築きましょう:

  • 関数型プログラミング
  • 関数スコープ
  • スコープの継承関係

[関連する推奨事項: Go ビデオ チュートリアル ]

1.1 前提知識

1.2.1 関数型プログラミング

関数型プログラミングはプログラミング パラダイムであり、問​​題を考察する方法です。すべての関数は小さな関数で構成されています。より大きな関数になると、関数のパラメーターもまた、関数であり、関数の戻り値も関数です。私たちの一般的なプログラミング パラダイムは次のとおりです。

  • 命令型プログラミング:
    • 主なアイデアは、コンピューターが実行するステップに焦点を当てる、つまり、最初に何をするかを段階的にコンピューターに指示することです。そして何をすべきか。
    • まず、問題を解決するための手順を標準化し、それを特定のアルゴリズムに抽象化し、次にそれを実装するための特定のアルゴリズムを作成します。一般に、言語が手続き型プログラミング パラダイムをサポートしている限り、それを「言語」と呼ぶことができます。 BASIC.Cなどの手続き型プログラミング言語
  • 宣言型プログラミング:
    • 主なアイデアは、コンピューターに何をすべきかを指示することですが、SQL、Web 用 HTML など、それを実行する方法は指定しません。プログラミングとCSS。
  • 関数型プログラミング:
    • それをどのように行うかではなく、何を行うかのみに焦点を当てています。宣言型プログラミングの痕跡はありますが、「関数は」ということに重点を置いています。最初の「ビット」の原理は、関数がパラメーター、変数、戻り値など、どこにでも出現できることを意味します。

関数型プログラミングは、オブジェクト指向プログラミングの反対と考えることができます。一般に、特定のプログラミング手法を重視するのは一部のプログラミング言語だけであり、ほとんどの言語ははマルチパラダイム言語であり、JavaScript、Go など、さまざまなプログラミング手法をサポートできます。

関数型プログラミングは考え方です。コンピューターの操作を関数の計算とみなします。コードを記述するための方法論です。実際には、関数型プログラミングについて話してから、クロージャについて話すべきです。 . クロージャ自体が関数型プログラミングの特徴の 1 つであるためです。

関数型プログラミングでは、関数は ファーストクラス オブジェクト です。つまり、関数は他の関数の入力パラメーター値として使用できます。関数から取得できます。戻り値は変更されるか、変数に割り当てられます。 (Wikipedia)

一般に純粋な関数型プログラミング言語では、プログラムの状態や変数オブジェクトを直接使用することはできません。関数型プログラミング自体は、共有状態,#の使用を避けることになっています。 # #変数の状態副作用を可能な限り回避します。

関数型プログラミングには一般に次のような特徴があります:

  • 関数は第一級市民です: 関数は最初に配置され、パラメーター、値の割り当て、および渡しとして使用できます。戻り値として使用できます。

  • 副作用はありません。関数は純粋に独立した状態を維持する必要があり、外部変数の値を変更することはできず、外部状態も変更しません。

  • 参照の透明性: 関数の動作は外部変数や状態に依存せず、同じ入力パラメータに対して、戻り値はどの場合でも同じである必要があります。

1.2.2 関数スコープ

スコープ (スコープ)、プログラミングの概念、一般的に言えば、で使用される名前プログラム コードの一部は常に有効/利用可能であるとは限りません。名前の利用可能性を制限するコード スコープは名前の scope です。

平たく言うと、関数スコープとは、関数が動作できる範囲を指します。関数はボックスのようなもので、その中に別のレイヤーが入っており、スコープは閉じたボックス、つまり関数のローカル変数として理解でき、ボックス内でのみ使用でき、独立したスコープになります。

Golang におけるクロージャの簡単な分析

#関数内のローカル変数が関数を抜けた後にスコープ外に飛び出てしまい、変数が見つかりません。 (外部関数のスコープには内部関数が含まれるため、内部関数は外部関数のローカル変数を使用できます。) たとえば、次の

innerTmep は関数スコープ外の変数を見つけることができませんが、 outerTemp は引き続き内部関数で使用できます。

Golang におけるクロージャの簡単な分析

どの言語であっても、基本的には、未使用のメモリ空間をリサイクルする特定のメモリ リサイクル メカニズムがあり、そのリサイクル メカニズムは一般に関数のスコープと同じです。ローカル変数がそのスコープから外れると再利用される可能性がありますが、まだ参照されている場合は再利用されません。

1.2.3 スコープの継承関係

いわゆるスコープ継承とは、先ほどの小さなボックスが外側の大きなボックスのスコープを継承し、取り出すことができることを意味します。小さな箱から直接、大きな箱の中のものを取り出します。ただし、大きな箱は、エスケープが発生しない限り、小さな箱の中のものを取り出すことはできません(エスケープは、小さな箱の中のものへの参照を与えるものとして理解でき、大きな箱は取り出すことができます)入手後すぐに使用してください。)一般的に、変数のスコープには次の 2 種類があります。

  • グローバル スコープ: どこにでも適用されます

  • ローカル スコープ: 一般コード ブロックです。 、関数またはパッケージ内で、 関数内で宣言/定義された変数は ローカル変数と呼ばれます スコープは関数の内部に限定されます

#1.2 クロージャの定義"ほとんどの場合、最初に理解してから定義するのではなく、最初に定義してから理解する",まず定義します。

理解できなくても問題ありません

:

クロージャは
関数とそのバンドルされた周囲の環境 (字句環境) への参照です。 、字句環境) の組み合わせ

。言い換えれば、クロージャを使用すると、開発者は内部関数から外部関数のスコープにアクセスできるようになります。クロージャは関数の作成時に作成されます。

#1 文の表現:

##クロージャ= 関数 参照環境クロージャ = 関数参照環境

Go 言語という単語は上記の定義には見つかりません。賢い学生はクロージャが言語とは何の関係もないことを知っておく必要があります。これは JavaScript や Go に固有のものではありませんが、関数型プログラミング言語 は固有のものであり、はい、正しくお読みいただけます。 関数型プログラミングをサポートする言語はすべてクロージャをサポートします。Go と JavaScript もそのうちの 2 つであり、現在のバージョンの 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 次求结果...
结果: 15
先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15
先获取函数,不求结果
等待一会
第 1 次求结果...
结果: 15

上記の実行結果からわかります:

閉じるパッケージの作成時に、参照される外部変数 count のコピーが 1 つ作成されています。つまり、それらが個別に呼び出されても問題ありません。 質問を続けます。**関数が 2 つの関数を返す場合、これは 1 つのクロージャですか、それとも 2 つのクロージャですか? ** 以下で練習してみましょう:

一度に 2 つの関数を返します。1 つは合計の結果を計算する関数、もう 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

が変更された値になります: <pre class="brush:js;toolbar:false;">等待一会 第 100 次求结果... 结果: 15</pre>これは、クロージャが実際に

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]。例:

業界で使用されている他の関数型プログラミング言語には、マルチパラダイム Scala[23]F# などがあります。 Wolfram言語Common LispStandard MLClojureなど。

私の個人的な観点から言えば、私は純粋な関数型プログラミングについては楽観的ではありませんが、将来的には高度なプログラミングのニーズのほぼすべてが関数型プログラミングの考え方を持つようになるだろうと信じています。特に Java が関数型プログラミングを採用することを楽しみにしています。私が知っている言語から判断すると、Go と JavaScript の関数型プログラミング機能は開発者に深く愛されています (もちろん、バグを書くと開発者は嫌いになるでしょう)。

最近急に人気が出てきたのは、世界が発展しメモリがどんどん大容量化し、その制約がほぼ解放されてきたからでもあります。

私は、世界はカラフルだと信じています。1 つのものが世界を支配することは絶対に不可能です。それはむしろ、100 の学派が争っていることです。同じことがプログラミング言語やプログラミング パラダイムにも当てはまります。将来的にはマスターが現れるかもしれません、そして最終的には人類社会の発展に最終的に一致するマスターが歴史によって選別されるでしょう。

プログラミング関連の知識について詳しくは、プログラミング ビデオをご覧ください。 !

以上がGolang におけるクロージャの簡単な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjuejin.cnで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。