Skip to Content

入門Goプログラミング Lesson14(ファーストクラス関数)から学んだこと

ファーストクラス関数

変数に関数を代入

realSensor()と、faceSensor()関数を作成
本物のセンサーを繋がるまでの間は、偽物のセンサーが擬似乱数を返す
互換性を実現する設計
sensorrealSensorを再代入できるのは、
どちらの関数も、同じ数、同じ型のパラメータと戻り値だから

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
	"fmt"
	"math/rand"
	"time"
)

type kelvin float64 // 独自の型を定義

func fakeSensor() kelvin {
	rand.Seed(time.Now().UnixNano()) // Seedに時刻を与える
	return kelvin(rand.Intn(151) + 151)
}

func realSensor() kelvin {
	return 0 // 後で本物のセンサーを実装する予定ってこと
}

func main() {
	sensor := fakeSensor // 関数を変数に代入する
	fmt.Println(sensor())

	sensor = realSensor // 関数を変数に代入する
	fmt.Println(sensor())
}

実行結果

261
0

関数を他の関数に渡す

関数に関数を渡すことはコードを細分化する協力な手段になります

  • 変数は関数に渡すことができる(一般的)
  • 変数は関数を参照できる(直前の変数に関数を代入で試した)
  • 関数を他の関数に渡すことができる、そうGoならね(他にできる言語があるのか知らない)

試してみたら面白かった、使いこなせればとても便利

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main

import (
	"fmt"
	"math/rand"
	"time"
)

type kelvin float64 // 独自の型を定義

// 追加
// 第2引数で関数を受け取る
func measureTemperture(samples int, sensor func() kelvin) {
	for i := 0; i < samples; i++ {
		k := sensor()
		fmt.Printf("%vo K\n", k)
		time.Sleep(time.Second)
	}
}

func fakeSensor() kelvin {
	rand.Seed(time.Now().UnixNano()) // Seedに時刻を与える
	return kelvin(rand.Intn(151) + 151)
}

func realSensor() kelvin {
	return 0 // 後で本物のセンサーを実装する予定ってこと
}

func main() {
    //追加
	// 関数を第2引数に
	measureTemperture(3, fakeSensor)
}

実行結果

155o K
202o K
171o K

関数型の宣言

関数に多数のパラメータがあるときは、型を使うことによってすっきりとさせられる

sensor型の関数

1
type sensor func() kelvin

sensor型を使ってコードを圧縮する
この例だけだと改善とは思えない
sensorをあいちこちで使う場合や、関数に多数パラメータがある場合に有効

使いこなせる自信がない

Befor

1
func measureTemperture(samples int, sensor func() kelvin)

After

1
func measureTemperture(samples int, s sensor)

クロージャと無名関数

普通の関数と違って、クロージャ
自分を囲むスコープにある変数への参照を保持できる

何を言っているのかさっぱりわからない

無名関数に変数を代入すれば、その変数を他の関数と同様に使える

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package main

import "fmt"

var f = func() { // 変数(f)に無名関数を代入
	fmt.Println("無名関数を使った")
}

func main() {
	f()
}

実行結果

無名関数を使った

変数(f)の宣言は、パッケージのスコープにあっても、関数内にあってもOK

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package main

import "fmt"

func main() {
	f := func(message string) { // 無名関数を変数に代入
		fmt.Println(message)
	}
	f("関数の中にあるパターン")
}

実行結果

関数の中にあるパターン

memo
無名関数の宣言と呼び出しを1ステップで行うことも可能

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
	func() { // 無名関数の宣言
		fmt.Println("省略パターン")
	}() // ()で関数を呼び出し
}

実行結果

省略パターン

memo
他の関数から既存の名前付き関数を返すことは可能ですが
新しい無名関数を宣言して返す方がずっと便利

難しくて何を言っているのかわからない

無名関数はスコープにある変数を囲い込むので、
クロージャ(閉包)という名前が付けられた
クロージャは、それを囲むスコープの変数の値をコピーするのではなく、
その変数へのリファレンスを保持するので、その変数を変更すると、
無名関数の呼び出しに反映されます

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import "fmt"

type kelvin float64

// sensor 関数型
type sensor func() kelvin

func realSensor() kelvin {
	return 2 // TODO:実数値で実装
}

func calibrate(s sensor, offset kelvin) sensor {
	return func() kelvin { // 無名関数を宣言して返す
		return s() + offset
	}
}

func main() {
	sensor := calibrate(realSensor, 5)
	fmt.Println(sensor())

}

実行結果

7

まとめ

  • 関数をファーストクラスと
  • メソッドは型に関数を割り当てた感じ

感想

手を動かしてみるまで全然イメージ掴めなかったけど、 実際に書いてみると理解できた
便利で型によって振る舞いが変わるから変なエラーの防止(信頼性)にも繋がりそうだ

参考

なし