goの3つのiterator
参考元
https://blog.kowalczyk.info/article/1Bkr/3-ways-to-iterate-in-go.html
github
https://github.com/kjk/go-cookbook/tree/master/3-ways-to-iterate
こちらのgolangのiteratorに関する記事が参考になったので、まとめました。
3つの common iteration patternがgoにはあるそうです。
- callbacks
- an iterator object with Next() method
- channels
callback
func iterateOddNums(max int, f func(n int) error) error {
if max < 0 {
return errors.New("max must be greater than 0")
}
for i := 1; i <= max; i += 2 {
err := f(i)
if err != nil {
return err
}
}
return nil
}
func printOddNumbers(max int) error {
err := iterateOddNums(max, func(n int) error {
fmt.Printf("%d\n", n)
return nil
})
return err
}
func main() {
if err := printOddNumbers(20); err != nil {
log.Fatal(err)
}
}
関数を引数でもらうpattern。
callback関数はiterateを中断するべきかどうかの情報を返す
filepath.Walk
で使われているpatternとのこと
next
type OddNumIterator struct {
max int
current int
err error
}
func NewOddNumIterator(max int) *OddNumIterator {
var err error
if max < 0 {
err = errors.New("max must be greater than 0")
}
return &OddNumIterator{
max: max,
current: 1,
err: err,
}
}
func (i *OddNumIterator) Next() bool {
if i.err != nil {
return false
}
i.current += 2
return i.current <= i.max
}
func (i *OddNumIterator) Value() int {
if i.err != nil || i.current > i.max {
panic("something wrong")
}
return i.current
}
func (i *OddNumIterator) Err() error {
return i.err
}
func printOddNums(max int) error {
iter := NewOddNumIterator(max)
for iter.Next() {
fmt.Printf("%d\n", iter.Value())
}
return iter.Err()
}
func main() {
if err := printOddNums(20); err != nil {
log.Fatal(err)
}
}
-
database/sql
のRows.Next
-
bufio
のScanner.Scan
等で利用されていると思います
goroutine safeにするには, sync.Mutex
を埋め込むべきか。
Value()
がintのみを返すために、中でpanicを呼ぶのも直したい
channel
type IntWithErr struct {
Int int
Err error
}
func generateOddNums(ctx context.Context, max int) <-chan IntWithErr {
ch := make(chan IntWithErr)
go func() {
defer close(ch)
if max < 0 {
ch <- IntWithErr{
Err: errors.New("max must be greater than 0"),
}
return
}
for i := 1; i <= max; i += 2 {
if ctx != nil {
select {
case <-ctx.Done():
return
default:
}
}
ch <- IntWithErr{
Int: i,
}
}
}()
return ch
}
func printOddNums(max int, stopAt int) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ch := generateOddNums(ctx, 20)
for val := range ch {
if val.Err != nil {
return val.Err
}
if val.Int > stopAt {
cancel()
break
}
fmt.Printf("%d\n", val.Int)
}
// drain
for _ = range ch {
}
return nil
}
func main() {
if err := printOddNums(100, 20); err != nil {
log.Fatal(err)
}
}
channelを利用するとpythonのgeneratorのように利用できるのだろうか。
cancel()
を読んでも、呼んだ先が処理を停止する保証がないので、channelからdrainしないとgoroutine leak(?) するとのこと。
go func() {
for _ = range ch {
}
}()
のようにdrain処理も別のgoroutineに切り出したほうが安全かなと思った。
どれがBestか
The one that best fits your scenario.
やっぱりそうですよね.