GolangのSliceでFilterやMap的なことをやりたい

前までGo言語でFilter的なことをやりたい時は`go-funk`を使っていたが、`gen`を使うようになってからコードが読みやすくなったように感じるのでオススメしたい。

# 方法1: go-funkを使う

[go-funk](https://github.com/thoas/go-funk)というライブラリを使うと、自前のstructのsliceに対してもFilterやMapなどの処理をかける。

```golang
result := funk.Filter(books, func(book *Book) bool {
    return book.Author == "John Smith"
})
```

Filter以外にも`Find`や`Map`、`Uniq`など非常に便利な関数が用意されている。手軽さを重視するなら、`go-funk`を使うのも悪くないとは思う。

ただし、go-funkはリフレクションの仕組みを使って実装されているため、返り値が`interface{}`型のsliceになっているため毎回キャストが必要になったり、ビルド時の型チェックが効かないといったデメリットがある。

```golang
books := []*Book{
	{Author: "John Smith", Title: "My life"},
	{Author: "John Smith", Title: "Money"},
	{Author: "Paul Brown", Title: "Your life"},
}

titles := funk.Map(funk.Filter(books, func(b *Book) bool {
	return b.Author == "John Smith"
}), func(b *Book) string {
	return b.Title
}) // []interface{}型になってしまう
```

ビルド時の型チェックが通らないことが問題で、例えばこんなコードを書いたとしてもビルドできてしまう。

```
// booksは*Bookのスライスなのに、判定用の関数の引数は*Songになっている
titles := funk.Filter(books, func(s *Song) bool {
	return song.Name == "Your song"
}) 
```

手軽で便利なのだが、使っているとこのやり方はGo的ではないように感じる。

また、FilterもMapも関数の引数に配列を渡す形になるため、入れ子構造になって読みにくい。できれば下記のように書きたい。

```
titles := books.Filter(...).Map(...)
```

# 方法2: genを使う
go-funkは気軽に使えるという点では良いのだが、もっと無理なくGoらしくFilterを実現する方法がないか調べていたところ、[gen](https://github.com/clipperhouse/gen)に辿り着いた。genはコードジェネレータなので、好みが分かれるかもしれないが、生成されるコードはビルド時の型チェックが有効になるし読みやすいので気に入っている。

genのコード生成を使ってFilter(Where)などのメソッドを生成すると下記のようなコードでFilterやMapを書けるようになる。

## インストール
```
go get github.com/clipperhouse/gen
```

## アノテーションを書く
```
// +gen * slice:"Where"
type Book struct {
	Author string
	Title  string
}
```

コード生成したい型の上に`+gen`から始まるアノテーションを書く。今回はスライス用のコード生成の機能を使っているが、`gen`には他のコード生成の機能も用意されている(があまり積極的に開発・利用されてなさそう)。

`slice:Where`は、スライス用のジェネレータを使って、`Where`メソッド用のコードを含める、という指示になっている。Where以外にも生成可能なメソッドは色々あるが、今回は使わないので省略した。[gen: slice typewriter](https://clipperhouse.com/gen/slice/)

`+gen`と`slice:`の間に`*`を書いているのは、スライスの要素をポインタでの参照にしたいため。これを書かないと、生成されるスライス`BookSlice`は`[]Book`となり、書くと`[]*Book`となる。


## 生成する
```
gen
```

`型名_slice.gen`というファイルが生成される。中には`型名Slice`という型と、メソッド`Where`が定義されている。

```
// BookSlice is a slice of type *Book. Use it where you would use []*Book.
type BookSlice []*Book

// Where returns a new BookSlice whose elements return true for func. See: http://clipperhouse.github.io/gen/#Where
func (rcv BookSlice) Where(fn func(*Book) bool) (result BookSlice) {
	for _, v := range rcv {
		if fn(v) {
			result = append(result, v)
		}
	}
	return result
}

```

非常にシンプルで難しいこと、複雑なことは何もやっていない。これだけなら`gen`を使わなくても手動で書いても問題ないが、`Where`以外の関数も使い始めるようになったり、あるいは色々な型でこれらの関数を使うようになってくると、やはりコード生成の便利さを感じるようになると思う。

## 使う
シンプルなコードなので説明するまでもないが、生成されたWhereメソッドを実際に使ってみる。

```
books := BookSlice{
	{Author: "John Smith", Title: "My life"},
	{Author: "John Smith", Title: "Money"},
	{Author: "Paul Brown", Title: "Your life"},
}

result := books.Where(func(b *Book) bool {
	return b.Author == "John Smith"
})
```

このWhere関数は、この型(Book)のスライスの関数なので、ビルド時の型チェックが有効であり、また返り値も`interface{}`型のスライスではなく、オリジナルの型のスライスとなる。

## 生成されたコードをさらに拡張する
これは`gen`の機能ではないのだが、`gen`の`Slice typewriter`で生成された`Slice`に、さらに自前で使いたいメソッドを追加で実装するとより便利になる。

自分の場合は`型名_slice_ext.go`というファイルを作成し、その中で`型Slice`に対してさらに良く使う機能を関数で実装している。

```
func (rcv BookSlice) Titles() []string {
	titles := make([]string, len(rcv))
	for i, b := range rcv {
		titles[i] = b.Title
	}
	return titles
}
```

こうすることで、genが生成するメソッドと組み合わせて下記のような書き方ができる。

```
titles := books.Where(func(b *Book) bool {
	return b.Author == "John Smith"
}).Titles()
```

型チェックが効き、それでいて読みやすくGo的で気に入っている。

# 結論
コード生成については好き嫌いが分かれるところだが、Goの言語自体にコード生成を補助する機構が用意されていることや、静的解析によるコンパイル時の型チェックの有効性などを考えると、`gen`の`slice typewriter`を使うという選択肢は割とありだと思う。実際に、仕事で多用するようになってから、`go-funk`を多用していた頃よりもコードが読みやすく書きやすくなったと感じている。エディタからショートカットで`gen`コマンドを呼び出せるようにしておいたりすると、ますます便利なのでぜひ試してみてほしい。