Go 1.23 Range over func
Go 1.22에서 experimental이었던 range over func 기능이 1.23에서 정식 포함되어, iterator를 구현하여 for range문의 순회 대상으로 넣을 수 있게 되었다.
iterator의 타입은 다음과 같다. 아래의 두 타입은 iter 패키지에 Seq, Seq2라는 이름으로 추가되어 있다.
func(func() bool)
func(func(K) bool)
func(func(K, V) bool)
위키의 Backward는 Seq2 구현에 대한 예제이고, 첫 번째 타입을 간단히 구현해 보면,
package main
import "fmt"
func seq(num int) func(func() bool) {
return func(yield func() bool) {
for _ = range num {
if !yield() {
return
}
}
}
}
func main() {
for range seq(5) {
fmt.Println("Hello, 世界")
}
}
Hello, 世界
Hello, 世界
Hello, 世界
Hello, 世界
Hello, 世界
이 때, 16째줄에서 range의 iteration variable을 작성하면(blank identifier이더라도) 컴파일 에러가 발생한다.
for _ = range seq(5) {
./prog.go:16:6: range over seq(5) (value of type func(func() bool)) permits no iteration variables
각 iterator가 인자로 받는 yield는 iterator를 순회하는 for range문의 loop body가 적절히 컴파일되어 호출되는 것이라고 한다. for range문의 iteration variable은 yield의 인자로, loop body 내의 continue는 yield의 return true로, break는 return false로 변환된다고 하므로 iterator 구현시에 참고해야겠다. (return false를 적절히 처리하지 않으면 panic이 발생할 것이라고 한다) loop body 내의 defer, panic도 다른 range문과 이질감 없게 작동할 것이라고 한다.
또한 iter 패키지는 Pull, Pull2 함수(Seq, Seq2에 각각 대응)를 제공하여 for range 문 외에 직접 iterator를 순회시킬 수 있도록 하고 있다. 위키의 Zip은 자기가 순회하면서, 인자로 받은 두 iterator를 Pull로 순회시키는 동작을 하는 예제이다. (python의 zip 함수와 비슷한 작동을 하도록 구현)
기존에 go로 여러 반복 로직을 작성할 때 굳이 slice, array 등을 만들어서 구현하느라 메모리를 사용했어야 하는 상황에 재미있게 사용 가능할 것으로 보인다. 여러가지 trivial하지 않은 상황을 연출해 보고 싶은데 천천히..
20230819
iterator 안에서 발생한 에러를 처리해야 할 수 있을 것 같은데 기본 제공되는 인터페이스로는 마땅한 방법이 없는 것 같아 workaround를 구상해 보았다.
package main
import (
"errors"
"fmt"
"iter"
)
type witer[E any] struct {
it iter.Seq[E]
err error
}
func seq(num int) *witer[int] {
w := &witer[int]{}
w.it = func(yield func(int) bool) {
for i := range num {
if i == 19 {
w.err = errors.New("boom")
break
}
if !yield(i) {
break
}
}
}
return w
}
func main() {
w := seq(25)
for i := range w.it {
fmt.Printf("%d\n", i) // 18까지 출력
}
fmt.Printf("err:%s\n", w.err) // err:boom
}
에러가 nil이 아니면 중간에 에러가 발생하여 iterator를 끝까지 순회하지 못한 것이다.
좀 지저분하다..
20230820
추가 타입 정의 없이, Seq도 클로저니까 클로저를 이용해서 짜본 버전.
package main
import (
"errors"
"fmt"
"iter"
)
func seq(num int) (iter.Seq[int], func() error) {
var err error
s := func(yield func(int) bool) {
for i := range num {
if i == 19 {
err = errors.New("boom")
break
}
if !yield(i) {
break
}
}
}
ef := func() error {
return err
}
return s, ef
}
func main() {
s, ef := seq(25)
for i := range s {
fmt.Printf("%d\n", i) // 18까지 출력
}
fmt.Printf("err:%s\n", ef()) // err:boom
}