概要
- 前回の続き
- 今回は関数について学習。
- 関数の宣言方法や、値渡しである点、クロージャやdefer等について
参考
関数の宣言方法
-
func 関数名(引数) 戻り値
で宣言する
func div(a int, j int) int {
return a / j
}
// 引数の型はまとめて宣言できる
func div2(a ,j int) int {
return a / j
}
// ...可変長引数(スライス)も可能
func addTo(base int, vals ...int) []int {
out := make([]int, 0,len(vals))
for _, v := range vals {
out = append(out, base+v)
}
return out
}
戻り値について
- 戻り値は複数定義できる。その場合
(型名,型名,...)
と()で括る。 - 名前付き戻り値
- 戻り値に名前(変数名)をつけることができる。処理の意図定義できる。
- ただし、シャドーイングの問題や、別の変数の値を返しても良いし、後述のブランクreturn問題もあるため、使用しないほうがよさそう。(後述するdeferは例外)
- ブランクreturn
- 名前付き戻り値の場合、returnの後を省略できる。
- 処理が追いづらくなるし、意図せず名前付き戻り値を書き換えることもあるため、使用しないほうがよさそう。
// 複数の戻り値
func multiAddFunc(add1 int, add2 int )(int, string) {
result := add1 + add2
return result, "足し算の結果"
}
// 名前付き戻り値
func funcName()(message string) {
message = "名前付き戻り値";
// 別の値を返しても良い
return "別の値を返す"
}
func brankFunc()(message string) {
message = "ブランクreturn"
return
}
func main() {
reslt, message := multiAddFunc(10,20)
fmt.Println(reslt,message) // 足し算の結果30
fmt.Println(funcName()) // 別の値を返す
fmt.Println(brankFunc()) // ブランクreturn
}
無名関数について
- 関数は値であるため、変数に代入することができる
- また、関数をインラインで記述して、即時に呼び出すこともでき、無名関数という
func main() {
//関数は値であるため変数に代入できる
var add = addFunc
fmt.Println(add(10, 20))
for i := 0; i < 5; i++ {
//無名関数-即時実行()する
func(j int) {
fmt.Println("無名関数を実行", j)
}(i)
}
}
func addFunc(val1 int, val2 int) int {
return val1 + val2
}
クロージャ
- 関数内(外側)で関数(内側)を定義した場合、内側から外側の変数にアクセスできること
- 内側の関数をクロージャと呼ぶ
- クロージャは外側関数からしかアクセスできないので、外部から隠すことができる
- クロージャを引数にすることで、外側変数を変更できる
- クロージャを返す(外側関数が内側関数を返す)ことで、クロージャの再利用、カリー化が行える。
クロージャを引数にする
func main() {
type Person struct {
name string
age int
}
person := []Person{
{"hase",30},
{"taro",20},
{"hana",40},
}
// クロージャを引数にする
// sort.Slice(ソート対象,ソート条件=クロージャ))
sort.Slice(person,func(i int, j int) bool {
// iとjは比較対象のインデックス
return person[i].age < person[j].age
})
fmt.Print(person) //年齢の昇順となる
}
クロージャを戻り値にする
func main() {
// カリー化
twoBase := makeMult(2) //2倍する関数
threeBase := makeMult(3) // 3倍する関数
for i := 0; i<5; i++ {
fmt.Println(i, "を2倍", twoBase(i))
fmt.Println(i, "を3倍", threeBase(i))
}
}
// クロージャを戻り値にする
func makeMult(base int) func(int) int {
return func(factor int) int {
return base * factor
}
}
defer
- 関数に付与し、ファイルのクローズ処理などのリソース開放を行う。
-
defer クローズ関数()
関数実行形式で定義する。 - クローズ関数は必ず実行され、実行されるタイミングは一番最後。(return文が終わった後。)
- Javaの
try-with-resources
と同様。または、finally
句。
- Javaの
- defer内の処理で、返却する値を設定したい場合(エラー処理時の後処理など)、名前付き戻り値が使われる
func dbInsert(引数は割愛)(err error) {
... //DBインサート処理などを実施
// 処理でエラーが発生したらerrを返却する
if err != nil {
return err
}
// 無名関数をdefer定義(即時実行する)
// 返却したerrをdefer関数が利用できる(returnの後に実行される)
// →クロージャ内で外側のerr変数を使用するのと同じ原理
defer func() {
if err == nil {
err = tx.Commit() //エラーがなければコミット
}
if err != nil {
err = tx.Rollback() // エラーの場合にロールバック
}
}() // 即時実行
}
関数の引数や戻り値に構造体を渡した場合は値渡しとなる
- 構造体を渡した場合も、構造体のコピーを作るため、呼び出し元の値は変更されない(Javaはインスタンスの参照値の値渡し)
- ただしスライスやマップは呼び出し元が変更される(Javaと同様参照値の値渡し)
- ※厳密にはプロパティで管理されているポインタ値を値渡ししてるらしい。
所感
- 戻り値についての特殊な動作はGo言語特有と感じた。
- 使い所が一部あるそうだが、バグの温床になりそうで非推奨にしたほうがよいのではと思う。
- 無名関数やクロージャ、第一級関数(関数が値)などは、tsと同じかな。
- deferについては、Go固有の文法であり、使いづらそうに感じた。
- try-catch-finallyだとインデントが深くなる
ため、このような形式となったらしい。 - だが、名前付き戻り値を使用するし、ここは他の言語と合わせてもよかったのではないか。
- try-catch-finallyだとインデントが深くなる
- 戻り値やdefer周りなどの使いづらさで、関数についはGoは少しイマイチに感じた