- Contents -
スライスを任意の個数毎に分割して処理したい場合がある。
汎用性を、考えてどんな型のスライスでも受け取れるようにしようとして、
[]interface{}
を扱おうとすると、[]int
や、[]string
, []T
のようなスライスを直接渡せない事に気付く。
[]interface{}
と、[]T
は、メモリ上の表現が違うので、そういった事が出来ないようだ。
https://golang.org/doc/faq#convert_slice_of_interface
Go1にはまだジェネリクスがないので、
おそらくこの問題は解決できないようです。
スライスのchunk関数
今回の場合は、スライスを渡すのではなく
スライスのインデックスを生成してくれる関数を作る事で、汎用的に書くことができます。
// 添字のfrom, to を表現する構造体を用意
type IndexChunk struct {
From, To int
}
func IndexChunks(length int, chunkSize int) <-chan IndexChunk {
ch := make(chan IndexChunk)
go func() {
defer close(ch)
for i := 0; i < length; i += chunkSize {
idx := IndexChunk{i, i + chunkSize}
if length < idx.To {
idx.To = length
}
ch <- idx
}
}()
return ch
}
この関数を利用する際は、for range でループし、From, To を使って添字アクセスすれば良い。
// []T どんな型スライスでもOK
s := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// 任意の個数
n := 4
for idx := range IndexChunks(len(s), n) {
// From, To を使って添字アクセスする
fmt.Println(s[idx.From:idx.To])
}
//===> [1 2 3 4]
//===> [5 6 7 8]
//===> [9]
[]interface{}
では、[]T
は渡せない
先に結論を書いてしまいましたが、
普通に実装しようとした場合、自分も下記のような関数を作って処理しようとしていました。
// []interface{} だから、どんな型のスライスでも渡せるんじゃない??
func SliceChunks(s []interface{}, chunkSize int) <-chan []interface{} {
...
}
[]interface{}
なら、もちろんそのまま引数に渡して使うことができます。
// []interface{} 型で定義してるからOK
s := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9}
for chunk := range SliceChunks(s, 4) {
fmt.Println(chunk)
}
//===> [1 2 3 4]
//===> [5 6 7 8]
//===> [9]
しかし、[]int
, []string
, []T
のように定義されたものは渡すことができません。
// []T 型で定義した場合はNG
ints := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
// コンパイルエラー
for chunk := range SliceChunks(ints, 4) {
fmt.Println(chunk)
}
cannot use ints (type []int) as type []interface {} in argument to SliceChunks
ほかの解決方法
最初に見せた、IndexChunks関数を使う事をおすすめしますが、
一応、他の実装をするのであれば、以下のような方法があります。
[]interface{}
型にコピーして、関数にわたす。
[]interface{}
と、[]T
はメモリ表現が違うため直接渡せないので、
新たにスライスを作成してコピーして使う。
// []interface{}にコピーする
copiedSlice := make([]interface{}, len(s))
for i, v := range s {
copiedSlice[i] = v
}
// コピーしたスライスを渡す
for chunk := range SliceChunks(copiedSlice, 4) {
// chunkは、[]interface{} なので、元の[]Tで扱いたい場合、再度コピーしないといけない。。
fmt.Println(chunk)
}
わざわざコピーするのが面倒ですね。
型に合わせて、それぞれに実装関数を作る。
汎用性は諦めて、[]T型に合わせた関数をそれぞれ用意しちゃうというもの。
関数を使う範囲が限られているのであれば、冗長ですがコレでいいと思います。
[]interface{}
じゃなくて、interface{}
という引数にしてしまう。
interface{}
であれば、本当にどんな値でも渡すことができます。
それが、スライスであってもOKということです。
本当にスライスかどうかはリフレクションを使って、判定して処理しちゃう。
関数の戻り値も、interface{}
になるので、型アサーションして扱う事になる。
何を渡したらいいかよくわからん関数になってしまいますし、コンパイル時に変な使い方してるかのチェックもできない。
あんまりおすすめしない方法ですね。