概要
今回のブログポストではGolangでFunction
(関数)について詳しく見て、関数を使う色んな方法について説明します。このブログポストで紹介するコードは次のリンクで確認できます。
関数の基礎
Golangで関数を扱う基礎的な方法は以前のブログポストを参照してください。
可変引数関数
Golangでは関数で次のように可変引数(Variable argument list)を使うことが出来ます。
fmt.Println()
fmt.Println(1)
fmt.Println(1, 2, 3, 4, 5)
Golangでは次のように...
キーワードを使って可変引数関数を作ることが出来ます。
func FUNCTION_NAME(param ...TYPE) TYPE {
// Code block
}
これを確認するためmain.go
ファイルを生成して次のように修正します。
package main
import "fmt"
func sum(nums ...int) int {
fmt.Printf("nums type: %T\n", nums)
sum := 0
for _, num := range nums {
sum += num
}
return sum
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5))
fmt.Println(sum(10, 20))
fmt.Println(sum())
}
これを実行すると下記のような結果が表示されます。
# go run main.go
nums type: []int
15
nums type: []int
30
nums type: []int
0
defer
Golangはdefer
と言う遅延実行構文を提供してます。defer
は関数が終了される直前実行されることが保証されます。
defer STATEMENT
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func print() {
defer fmt.Println("World!")
fmt.Println("Hello")
}
func main() {
print()
}
これを実行すると次のような結果が表示されることが確認できます。
# go run main.go
Hello
World!
defer
はファイルを読むためファイルを開くなど、おもじOSリソースを返すとき使います。これを確認するためmain.go
ファイルを次のように修正します。
package main
import "os"
func readFile() {
f, err := os.Open("file.txt")
if err != nil {
return
}
f.Close()
}
func readFileDefer() {
f, err := os.Open("file.txt")
defer f.Close()
if err != nil {
return
}
}
func main() {
readFile()
readFileDefer()
}
defer
を使わない場合、Close
関数前で、エラーが発生すると、Close
関数はコールされません。
func readFile() {
f, err := os.Open("file.txt")
if err != nil {
return
}
f.Close()
}
しかし、defer
を使うとエラーが発生しても、Close
関数のコールが保証されます。
func readFileDefer() {
f, err := os.Open("file.txt")
defer f.Close()
if err != nil {
return
}
}
関数タイプ変数
Golangでは関数もタイプで使うことが出来ます。ここで関数タイプ変数とは関数を値で持つ変数を言います。関数もメモリにアドレスを持ってるので、変数に割り当てることが出来ます。
関数タイプは次のように関数シグネチャー
(Function signature)で表現します。
// Function
func add(a, b int) int {
return a + b
}
// Signature
func (int int) int
これを確認するため、main.go
ファイルを次のように修正します。
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func multiple(a, b int) int {
return a * b
}
func getOperator(op string) func(int, int) int {
if op == "+" {
return add
} else if op == "*" {
return multiple
} else {
return nil
}
}
func main() {
var op func(int, int) int
op = getOperator("+")
result := op(10, 20)
fmt.Println(result)
op = getOperator("*")
result = op(10, 20)
fmt.Println(result)
}
これを実行すると次のような結果が表示されます。
# go run main.go
30
200
関数シグネチャーのエイリアスタイプ
関数シグネチャーにエイリアスタイプを使うともっと簡単に関数タイプを使えます。
// Function signature alias type
type OperateFunc func(int, int) int
func getOperator(op string) OperateFunc {
}
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
type OperateFunc func(int, int) int
func add(a, b int) int {
return a + b
}
func multiple(a, b int) int {
return a * b
}
func getOperator(op string) OperateFunc {
if op == "+" {
return add
} else if op == "*" {
return multiple
} else {
return nil
}
}
func main() {
var op OperateFunc
op = getOperator("+")
result := op(10, 20)
fmt.Println(result)
op = getOperator("*")
result = op(10, 20)
fmt.Println(result)
}
これを実行すると次のような結果が表示されます。
# go run main.go
30
200
エイリアスタイプを使うと、関数シグネチャーをもっと簡単に使えることが分かります。
関数リテラル
Golangでも関数リテラル(匿名関数)を使うことが出来ます。
f := func(a, b int) int {
}
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
type OperateFunc func(int, int) int
func getOperator(op string) OperateFunc {
if op == "+" {
return func(a, b int) int {
return a + b
}
} else if op == "*" {
return func(a, b int) int {
return a * b
}
} else {
return nil
}
}
func main() {
var op OperateFunc
op = getOperator("+")
result := op(10, 20)
fmt.Println(result)
op = getOperator("*")
result = op(10, 20)
fmt.Println(result)
}
これを実行すると次のような結果が表示されます。
# go run main.go
30
200
Golangでは一般的に関数は状態を持つことができませんが、関数リテラルは内部に状態を持つことができます。これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func main() {
i := 0
f := func() {
i += 10
}
i++
f()
fmt.Println(i)
}
これを実行すると次のような結果が表示されます。
# go run main.go
11
一般関数は外部で宣言された変数にアクセスがでkないですが、関数リテラルは外部に宣言された変数にアクセス(キャプチャ)して使うことができます。ここでキャプチャ(Captured)された値は値コピーではなくリファレンスコピーです。つまり、ポインターがコピーされると言えます。
これを確認するためmain.go
ファイルを次のように修正します。
package main
import "fmt"
func CaptureLoop1() {
f := make([]func(), 3)
fmt.Println("CaptureLoop1")
for i := 0; i < 3; i++ {
f[i] = func() {
fmt.Println(i)
}
}
for i := 0; i < 3; i++ {
f[i]()
}
}
func CaptureLoop2() {
f := make([]func(), 3)
fmt.Println("CaptureLoop2")
for i := 0; i < 3; i++ {
v := i
f[i] = func() {
fmt.Println(v)
}
}
for i := 0; i < 3; i++ {
f[i]()
}
}
func main() {
CaptureLoop1()
CaptureLoop2()
}
これを実行すると次のような結果が表示されます。
# go run main.go
CaptureLoop1
3
3
3
CaptureLoop2
0
1
2
CaptureLoop1
はfor
に変数i
のインスタンスを直接参照してるので、i
の最後の値である3が全て出力されます。しかし、CaptureLoop2
はi
の値をv
にコピーしてv
インスタンスを参照していてv
はforループで毎回新しく生成されるので、0~2まで全ての値が出力されることが確認できます。
依存性注入
Golangでは関数タイプを使って依存性注入(DI - Dependency Injection)を使うことができます。依存性注入とはビジネスロジックを内部に存在することではなく外部からロジックを注入されることを意味します。
package main
import "fmt"
type PrintConsole func(string)
func PrintHello(p PrintConsole) {
p("Hello, World!")
}
func main() {
PrintHello(func(msg string) {
fmt.Println(msg)
})
}
これを実行してみると次のような結果が表示されます。
# go run main.go
Hello, World!
PrintHello
はPrintConsole
がどの役割(ビジネスロジック)をやるか分からないです。PrintHello
はPrintConsole
を通じて依存性が注入されたと言えます。
完了
これで関数についてもっと深く見て見ました。それで関数を活用する色んな方法についても見て見ました。ここで紹介した方法を使ってもっと素敵なコードを作成して見てください。
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Deku
が開発したアプリを使ってみてください。Deku
が開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。