順を追って学ぶ range over func
どの記事見てもなんのこっちゃわからんので、手を動かして少しづつ理解していきたい。
まず range over int
から
range にint を渡して、その数分実行する
package main
import "fmt"
func main() {
// range over int
for i := range 5 {
fmt.Printf("i: %d "i)
}
}
配列も渡せる
package main
import "fmt"
func main() {
// range over int
for i, v := range []int{1, 2, 3} {
fmt.Printf("i: %d, v: %d\n", i, v)
}
}
続いて range over func
。range over int
と比べると、int を渡していたのを func を渡せるようになっている。
package main
import "fmt"
func main() {
// range over func
for v := range rof {
fmt.Println("v is: ", v)
}
}
func rof(yield func(int) bool) {
for _, i := range []int{1, 2, 4} {
if !yield(i) {
return
}
}
}
出力は
v is: 1
v is: 2
v is: 4
rof が1度実行され、その中のfor 文が実行される。
1, 2, 4 がログに表示される。
yield の条件を反転させてみる
package main
import "fmt"
func main() {
// range over func
for v := range rof {
fmt.Println("v is: ", v)
}
}
func rof(yield func(int) bool) {
for _, i := range []int{1, 2, 4} {
if yield(i) { // ! を削除
return
}
}
}
結果は
v is: 1
return をしないようにしてみる
package main
import "fmt"
func main() {
// range over func
for v := range rof {
fmt.Println("v is: ", v)
}
}
func rof(yield func(int) bool) {
for _, i := range []int{1, 2, 4} {
yield(i)
}
}
結果は
v is: 1
v is: 2
v is: 4
つまり、yield を呼び出した時に実行されるのはfor ループ内部の処理
引数がyield に渡され、その渡された引数を元に関数を実行する
気になるのは yield
。
関数名を変更してみると。
package main
import "fmt"
func main() {
// range over func
for v := range rof {
fmt.Println("v is: ", v)
}
}
func rof(y func(int) bool) {
for _, i := range []int{1, 2, 4} {
if !y(i) {
return
}
}
}
結果は
v is: 1
v is: 2
v is: 4
関数の名称はyeild
出なくても良い
yeild
じゃなくても良いが、再帰的な関数は慣習としてyeild
と呼ばれるらしい
動作の順番は下記がわかりやすかった
yeild が返すのはbool なので、print してみる
package main
import "fmt"
func main() {
// range over func
for v := range rof {
fmt.Println("v is: ", v)
}
}
func rof(y func(int) bool) {
for _, i := range []int{1, 2, 4} {
fmt.Println(y(i))
}
}
結果は下記。ループの内容が実行された後、関数の返り値がtrue で返却されている
v is: 1
true
v is: 2
true
v is: 4
true
ループがfalse を返すようにしてみる
package main
import "fmt"
func main() {
// range over func
for v := range rof {
if v%2 == 0 {
fmt.Println("v is: ", v)
} else {
break
}
}
}
func rof(y func(int) bool) {
for _, i := range []int{1, 2, 4} {
fmt.Println(y(i))
}
}
結果は
false
panic: runtime error: range function continued iteration after function for loop body returned false
goroutine 1 [running]:
main.main-range1(0x100e20fd8?)
/Users/t17148/GolandProjects/awesomeProject/main.go:13 +0xc8
main.rof(0x1400010af28)
/Users/t17148/GolandProjects/awesomeProject/main.go:28 +0x58
main.main()
/Users/t17148/GolandProjects/awesomeProject/main.go:13 +0x3c
panic: runtime error: range function continued iteration after function for loop body returned false
↓
false を返したのに、rof のループが続いているのが嫌。
false を返された時は、yield も止まるようにしてあげないといけないので、合わせる
package main
import "fmt"
func main() {
// range over func
for v := range rof {
if v%2 == 0 {
fmt.Println("v is: ", v)
} else {
break
}
}
}
func rof(y func(int) bool) {
for _, i := range []int{1, 2, 4} {
res := y(i)
fmt.Println("res is: ", res)
if !res {
return
}
}
}
ループの終了をbreak してしまうとpanic しないが、何も表示されなかった。
continue にしてみると、思惑通り偶数のログだけ表示されるが、ループからの返り値は全てtrue となっていたので、break の時だけfalse が返ってくる
つまり、range over func を使うための関数は、false が返ってきた時の処理を考えておく必要がある。
false が返されるのはrange が履行されずにbreak された時。
break されればその後ループは実行されない。もちろんrange over func の中身も実行されない
key, value のrange over func
package main
import "fmt"
func main() {
for k, v := range rofKV {
fmt.Printf("from root k: %s, v: %s\n", k, v)
}
}
func rofKV(yield func(string, string) bool) {
kvMap := map[string]string{
"1": "one",
"2": "two",
"3": "three",
}
for k, v := range kvMap {
if yield(k, v) {
fmt.Printf("k: %s, v: %s\n", k, v)
}
}
}
結果は
from root k: 1, v: one
k: 1, v: one
from root k: 2, v: two
k: 2, v: two
from root k: 3, v: three
k: 3, v: three
itor 形に置き換えてみる。range over func の形はitor パッケージに実装された2つの方と関数に置き換えることができる。
// 型
type Seq[V any] func(yield func(V) bool)
type Seq2[K, V any] func(yield func(K, V) bool)
// 関数
func Pull[V any](seq Seq[V]) (next func() (V, bool), stop func())
func Pull2[K, V any](seq Seq2[K, V]) (next func() (K, V, bool), stop func())
range over func | iter |
---|---|
func(func() bool) | x |
func(func(K) bool) | Seq[V any] |
func(func(K, V) bool) | Seq2[K,V any] |
という対応関係。Seq2 を使った処理に置き換えてみる
package main
import (
"fmt"
"iter"
)
func main() {
kvMap := map[string]string{
"1": "one",
"2": "two",
"3": "three",
}
for k, v := range rofKV(kvMap) {
fmt.Printf("from root k: %s, v: %s\n", k, v)
}
}
func rofKV(kvMap map[string]string) iter.Seq2[string, string] {
return func(yield func(string, string) bool) {
for k, v := range kvMap {
if yield(k, v) {
fmt.Printf("k: %s, v: %s\n", k, v)
}
}
}
}
主な変更点
- kvMap が関数の外に出された
- 関数が返すのがSeq2 型になった