こちらは Voicy Advent Calendar 2021 20日目の記事です
先週の12/14にGo 1.18 Betaがリリースされました!注目はなんといっても去年のアドベントカレンダーでも書いたジェネリクスです!
さっそくインストール
こちらからインストーラーをダウンロードできますが、1.18出たぞわーいってノリで仕事で使ってるMacでpkgファイルを実行すると、既存のgoコマンドを上書きして
$ go version go version go1.18beta1 darwin/amd64
となってしまい、仕事に支障がでるのでやめましょう、、
ダウンロードページにも書いてありますが、その場合は
$ go install golang.org/dl/go1.18beta1@latest
と打つと~/go/bin/go1.18beta1
がインストールされるので、これを実行するとさらに
$ go1.18beta1 go1.18beta1: not downloaded. Run 'go1.18beta1 download' to install to /Users/[user name]/sdk/go1.18beta1
と出るので、言われるがままにgo1.18beta1 download
を実行すると無事go1.18beta1
という名前でコマンドがインストールされました!わーい!
$ go1.18beta1 version go version go1.18beta1 darwin/amd64
インストールができない場合はThe Go PlaygroundのGo dev branch
モードでも動作を確認することができます。
Filter/Mapを作ってみよう
ジェネリクスができたら個人的にぜひ欲しいと思ってたのが、配列に対して行うfilter
やmap
といった処理です。将来的には実装されるんじゃないかと思ってますが(もしくは1.18ですでに追加されていたら教えてください)、せっかくなので自前で作ってみようと思います。
Filterを作る
呼び出し方の理想としては
array := []int{100, 200, 300, 400, 500} filtered := array.filter(func(num int) bool { return num >= 300 })
みたいに書けて、なんなら関数の部分も省略して
array := []int{100, 200, 300, 400, 500} filtered := array.filter(num => num >= 300)
とか書けると良かったのですが、そうもいかず、、
結果的にはFilter
関数を作成して以下のように書くことにしました。とはいえ書き方はappend
とかと似てるのでそこまで違和感はありませんが。
array := []int{100, 200, 300, 400, 500} filtered := Filter(array, func(num int) bool { return num >= 300 })
というわけでジェネリクスを使用したFilter
関数はこうなりました。
func Filter[T any](array []T, f func(T) bool) (result []T) { for _, value := range array { if f(value) { result = append(result, value) } } return }
まぁそうだよねという感じで、特に難しいことはないと思います。
Mapを作る
同じ感じでMap
関数も作ってみます
呼び出し方はFilterと同じくこんな感じ。
array := []int{100, 300, 500, 700, 900} mapped := Map(array, func(num int) string { return "V" + strconv.Itoa(num) })
で、Map
関数はこうなりました。こちらはもっとシンプルですね。
func Map[T , T2 any](array []T, f func(T) T2) (result []T2) { for _, value := range array { result = append(result, f(value)) } return }
Filter/Mapを連続して呼び出したい
とはいえ、filterやmapってこんな感じで繋げて呼び出したいじゃないですか。
array := []int{100, 300, 500, 700, 900} newArray := Filter(・・・).Map(・・・)
でもこの関数の作り方だとこうやって個別に呼びだして、都度変数に入れる必要があるんですよね。
array := []int{100, 300, 500, 700, 900} filtered := Filter(array, ・・・) mapped := Map(filtered, ・・・)
めんどくさい、、なによりカッコ悪い、、
なので、繋げて呼び出せるようにJavaのStreamを参考にstruct
を作って、それにFilter/Map関数を持たせるようにしたらどうだろうと思い試してみました。
呼び出し方はこんなイメージです。
array := []int{100, 300, 500, 700, 900} result := NewStream(array). Filter(・・・). Map(・・・). Collect()
そしてStream
のstruct
はこんなイメージ。
func NewStream[T any](array []T) Stream[T, T2] { return Stream[T, T2]{array} } type Stream[T, T2 any] struct { array []T } func (s Stream[T, T2]) Filter(f func(T) bool) Stream[T, T2] {・・・} func (s Stream[T, T2]) Map(f func(T) T2) Stream[T, T2] {・・・} func (s Stream[T, T2]) Collect() []T { return s.array }
だったのですが・・・結論としてはダメでした、、
原因はMap
のところで戻り値に指定する必要のあるT2
です。本来はNewStreamでStream
を作る時にはT2
の型が確定していなければなりません。しかしT2
の型はMap
メソッドを呼び出す時の引数で決まるので、Stream
オブジェクトの作成時点では確定ができず、結果的にStream
の作成ができませんでした。
試しにStream
を作る時はT
のみを定義し、Map
を呼び出す時に初めてT2
が出てくるように
type Stream[T any] struct { array []T } func (s Stream[T]) Map[T2 any](f func(T) T2) Stream[T2] {・・・}
としてみたのですが、
methods cannot have type parameters
と言われてしまいました。メソッド毎に新しく型を追加することはできないようです、、
まとめ
他にも試行錯誤してみたのですが、結果的に良い感じのFilter/Mapを作成することができませんでした、、とはいえやっぱりあると便利なので、今後配列に標準でfilter
やmap
が使えるようになることを期待しています。
ジェネリクス以外にも1.18のアップデートはこちらに載っているので、興味のある方はぜひチェックしてみてください。
最後に
Voicyでは一緒に働くエンジニアを大募集中です!Goはもちろん、インフラ、アプリ、Web等さまざまな職種で応募をお待ちしております!詳しくは以下のページをご覧ください! https://hrmos.co/pages/voicy/jobs/0000121