Golangでコレクションに対して手軽にMapやFindがしたい

golangでは、コレクションを扱うときには主にfor文を使うと思います。
これは「goらしい」書き方だとは思うのですが、純粋な気持ちで書いているとfor文まみれになってしまって、コードが読みづらくなってしまうことも。

golangでも、他言語によくある`Map`や`Find`、`Fillter`などの関数があったらいいのに…。

[thoas/go-funk](https://github.com/thoas/go-funk)はそんな願いを叶えてくれるライブラリです。
`Map`や`Find`などの便利なヘルパ関数を提供していて、コレクションに対して手軽これらの操作を行うことができます。
他にもいろいろな便利な関数が提供されているので、今回は特に業務でよく使うものをいくつか紹介していきます。

※ この記事は[Fringe81 Advent Calendar](https://qiita.com/advent-calendar/2020/fringe81) 8日目の記事です。

## 注意点
最初に注意点を述べますが、`go-funk`で提供てされている多くの関数はreflectを使って実装されているため、それらの関数は返り値が`interface{}`型になることに注意が必要です。
typesafeな関数が型ごとに別で提供されている場合が多いので、できるだけそちらを利用するようにしましょう。
加えて、reflectを使って実装されている関数は、typesafeな実装に比べてパフォーマンスで劣るため、繰り返しになりますが使いたい型のtypesafeな関数が提供されている場合はそちらを使用するべきです。

## 使う
```sh
go get github.com/thoas/go-funk
```

```go
import "github.com/thoas/go-funk"
```

## メソッドの紹介
以下、サンプルコードは[公式ドキュメント](https://github.com/thoas/go-funk/blob/master/README.rst)より転載しています。

サンプルで用いるデータモデル

```go
type Foo struct {
    ID        int
    FirstName string `tag_name:"tag 1"`
    LastName  string `tag_name:"tag 2"`
    Age       int    `tag_name:"tag 3"`
}

func (f Foo) TableName() string {
    return "foo"
}

f := &Foo{
    ID:        1,
    FirstName: "Foo",
    LastName:  "Bar",
    Age:       30,
}
```

### funk.Contains
iterateeの中に指定された要素があればtrueを返す。

```go
// slice of string
funk.ContainsString([]string{"foo", "bar"}, "bar") // true

// slice of Foo ptr
funk.Contains([]*Foo{f}, f) // true
funk.Contains([]*Foo{f}, nil) // false

b := &Foo{
    ID:        2,
    FirstName: "Florent",
    LastName:  "Messa",
    Age:       28,
}

funk.Contains([]*Foo{f}, b) // false

// string
funk.Contains("florent", "rent") // true
funk.Contains("florent", "foo") // false

// even map
funk.Contains(map[int]string{1: "Florent"}, 1) // true
```

typesafeな実装は以下。
	- [ContainsFloat32](https://godoc.org/github.com/thoas/go-funk#ContainsFloat32)
	- [ContainsFloat64](https://godoc.org/github.com/thoas/go-funk#ContainsFloat64)
	- [ContainsInt](https://godoc.org/github.com/thoas/go-funk#ContainsInt)
	- [ContainsInt32](https://godoc.org/github.com/thoas/go-funk#ContainsInt32)
	- [ContainsInt64](https://godoc.org/github.com/thoas/go-funk#ContainsInt64)
	- [ContainsString](https://godoc.org/github.com/thoas/go-funk#ContainsString) 
	- [ContainsUint](https://godoc.org/github.com/thoas/go-funk#ContainsUInt)
	- [ContainsUint32](https://godoc.org/github.com/thoas/go-funk#ContainsUInt32)
	- [ContainsUint64](https://godoc.org/github.com/thoas/go-funk#ContainsUInt64)

### funk.Difference
2つのコレクションの差を返す。

```go
funk.Difference([]int{1, 2, 3, 4}, []int{2, 4, 6})  // []int{1, 3}, []int{6}
funk.Difference([]string{"foo", "bar", "hello", "bar"}, []string{"foo", "bar"})  // []string{"hello"}, []string{}
```
typesana実装は以下。
	- [Difference](https://godoc.org/github.com/thoas/go-funk#Difference)
	- [DifferenceInt](https://godoc.org/github.com/thoas/go-funk#DifferenceInt)
	- [DifferenceInt32](https://godoc.org/github.com/thoas/go-funk#DifferenceInt32)
	- [DifferenceInt64](https://godoc.org/github.com/thoas/go-funk#DifferenceInt64)
	- [DifferenceString](https://godoc.org/github.com/thoas/go-funk#DifferenceString)
	- [DifferenceUInt](https://godoc.org/github.com/thoas/go-funk#DifferenceUInt)
	- [DifferenceUInt32](https://godoc.org/github.com/thoas/go-funk#DifferenceUInt32)
	- [DifferenceUInt64](https://godoc.org/github.com/thoas/go-funk#DifferenceUInt64)

### funk.Filter
スライス内の、特定の条件を満たす要素をフィルタする。
条件は関数として渡す。

```go
r := funk.Filter([]int{1, 2, 3, 4}, func(x int) bool {
    return x%2 == 0
}) // []int{2, 4}
```
typesafeな実装は以下。
	- [FilterInt](https://godoc.org/github.com/thoas/go-funk#FilterInt)
	- [FilterInt32](https://godoc.org/github.com/thoas/go-funk#FilterInt32)
	- [FilterInt64](https://godoc.org/github.com/thoas/go-funk#FilterInt64)
	- [FilterFloat32](https://godoc.org/github.com/thoas/go-funk#FilterFloat32)
	- [FilterFloat64](https://godoc.org/github.com/thoas/go-funk#FilterFloat64)
	- [FilterString](https://godoc.org/github.com/thoas/go-funk#FilterString) 
	- [FilterUInt](https://godoc.org/github.com/thoas/go-funk#FilterUInt)
	- [FilterUInt32](https://godoc.org/github.com/thoas/go-funk#FilterUInt32)
	- [FilterUInt64](https://godoc.org/github.com/thoas/go-funk#FilterInt64)

### funk.Find
スライス内に、特定の条件を満たす要素があるかどうかを返す。
条件は関数として渡す。

```go
r := funk.Find([]int{1, 2, 3, 4}, func(x int) bool {
    return x%2 == 0
}) // 2
```
typesafeな実装は以下。
	- [FindInt](https://godoc.org/github.com/thoas/go-funk#FindInt)
	- [FindInt64](https://godoc.org/github.com/thoas/go-funk#FindInt64) 
	- [FindFloat32](https://godoc.org/github.com/thoas/go-funk#FindFloat32) 
	- [FindFloat64](https://godoc.org/github.com/thoas/go-funk#FindFloat64) 
	- [FindString](https://godoc.org/github.com/thoas/go-funk#FindString) 

### funk.Map
map/sliceに対して特定の操作を行う。
他の型に変換することもできる(map←→slice)

```go
r := funk.Map([]int{1, 2, 3, 4}, func(x int) int {
    return x * 2
}) // []int{2, 4, 6, 8}

r := funk.Map([]int{1, 2, 3, 4}, func(x int) string {
    return "Hello"
}) // []string{"Hello", "Hello", "Hello", "Hello"}

r = funk.Map([]int{1, 2, 3, 4}, func(x int) (int, int) {
    return x, x
}) // map[int]int{1: 1, 2: 2, 3: 3, 4: 4}

mapping := map[int]string{
    1: "Florent",
    2: "Gilles",
}

r = funk.Map(mapping, func(k int, v string) int {
    return k
}) // []int{1, 2}

r = funk.Map(mapping, func(k int, v string) (string, string) {
    return fmt.Sprintf("%d", k), v
}) // map[string]string{"1": "Florent", "2": "Gilles"}
```
返り値が`interface{}`になることに注意。
型変換しないと以降の処理で利用できない。

### funk.Keys
Map型のキーをsliceで返す。

```go
funk.Keys(map[string]int{"one": 1, "two": 2}) // []string{"one", "two"} (iteration order is not guaranteed)
```
typesafeなメソッドはないので、型変換する必要がある。

### funk.Uniq
sliceの値を一意にする。

```go
funk.Uniq([]int{0, 1, 1, 2, 3, 0, 0, 12}) // []int{0, 1, 2, 3, 12}
```
typesafeな実装は以下
	- [UniqInt](https://godoc.org/github.com/thoas/go-funk#UniqInt)
	- [UniqInt32](https://godoc.org/github.com/thoas/go-funk#UniqInt32)
	- [UniqInt64](https://godoc.org/github.com/thoas/go-funk#UniqInt64)
	- [UniqFloat32](https://godoc.org/github.com/thoas/go-funk#UniqFloat32)
	- [UniqFloat64](https://godoc.org/github.com/thoas/go-funk#UniqFloat64)
	- [UniqString](https://godoc.org/github.com/thoas/go-funk#UniqString) 
	- [UniqUInt](https://godoc.org/github.com/thoas/go-funk#UniqUInt)
	- [UniqUInt32](https://godoc.org/github.com/thoas/go-funk#UniqUInt32)
	- [UniqUInt](https://godoc.org/github.com/thoas/go-funk#UniqUInt64)

### funk.Subtract
2つのコレクション間の減算結果を返す。順序を保持する。

```go
funk.Subtract([]int{0, 1, 2, 3, 4}, []int{0, 4}) // []int{1, 2, 3}
funk.Subtract([]int{0, 3, 2, 3, 4}, []int{0, 4}) // []int{3, 2, 3}
```
stringのみtypesafeな実装が提供されている。 → [SubtractString](https://godoc.org/github.com/thoas/go-funk#SubtractString)

## おまけ `interface{}`からの型変換

goでは、実体の型が何であるかを動的にチェックする仕組みとして**型アサーション**が提供されています。

↓のように書くことができます。
Tは変換したい型で、第1返り値は型アサーションが成功したときの実際の値が、第2返り値は型アサーションが成功したかどうかがboolで返ります。

```go
v, ok := x.(T)
```

もしくは、変換候補が複数ある場合は、**型switch文**を使って以下のようにも書けます。

```go
switch x.(type) {
case string:
	s := x.(string)
case int:
	i := x.(int)
}
```

参考
- [funk - GoDoc](https://godoc.org/github.com/thoas/go-funk)
- [thoas/go-funk](https://github.com/thoas/go-funk)