はじめに
Go 1.23で追加されたrange over funcとiterパッケージを使ってみたかったのでイテレータを使ってCSVを読んでみます
導入の経緯としては、いろいろなパッケージのイテレーション処理のインターフェイスが異なっているから統一したいね、ってことだと認識してます
基本的な使い方などは「Go イテレータ」「Go range over func」とかで検索するといくつかの記事で紹介されています
これまでのCSVの読み方
無限ループの中で一行ずつ取り出し、io.EOF
がエラーとして返されたらbreak
するみたいな流れです
これまでのCSVの読み方
func HandleCSV(r io.Reader) {
csvReader := csv.NewReader(r)
for {
record, err := csvReader.Read()
if err == io.EOF {
break
}
if err != nil {
エラーハンドリング()
}
何かの処理()
}
}
イテレータを使ってみる
*csv.Reader
を受け取って、イテレータを返す関数を作りました
代入文やio.EOF
のエラーハンドリングが消えてメインの処理への流れが読みやすくなった気がします
イテレータを使ってみる
func HandleCSV(r io.Reader) {
csvReader := csv.NewReader(r)
for record, err := range csvRecords(csvReader) {
if err != nil {
エラーハンドリング()
}
何らかの処理()
}
}
// 一行ずつのイテレータを返す
func csvRecords(r *csv.Reader) iter.Seq2[[]string, error] {
return func(yield func([]string, error) bool) {
for {
record, err := r.Read()
if err == io.EOF {
return
}
if !yield(record, err) {
return
}
}
}
}
おまけ:構造体にマッピングもイテレータで
使用数がおそらく一番多いgocsvではなく、一行ごとのマッピングが簡単にできるcsvutilを使います
構造体の定義は端折りますが、encoding/jsonと同じくタグで定義できます
マッピングしたい構造体はUser
ということにします
イテレータを使ってマッピング
func HandleCSV(r io.Reader) {
csvDecoder, err := csvutil.NewDecoder(csv.NewReader(r))
if err != nil {
log.Fatal(err) // ヘッダーすらないとき
}
for user, err := range users(csvDecoder) {
if err != nil {
エラーハンドリング()
}
何らかの処理()
}
}
// 一行ずつのイテレータを返す
func users(d *csvutil.Decoder) iter.Seq2[User, error] {
return func(yield func(User, error) bool) {
for {
var user User
err := d.Decode(&user)
if err == io.EOF {
return
}
if !yield(user, err) {
return
}
}
}
}