Open16

順を追って学ぶ range over func

KatEusKatEus

どの記事見てもなんのこっちゃわからんので、手を動かして少しづつ理解していきたい。
まず 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)
	}
}

KatEusKatEus

続いて range over funcrange 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
		}
	}
}



KatEusKatEus

出力は

v is:  1
v is:  2
v is:  4

rof が1度実行され、その中のfor 文が実行される。
1, 2, 4 がログに表示される。

KatEusKatEus

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
KatEusKatEus

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
KatEusKatEus

つまり、yield を呼び出した時に実行されるのはfor ループ内部の処理
引数がyield に渡され、その渡された引数を元に関数を実行する

KatEusKatEus

気になるのは 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 出なくても良い

KatEusKatEus

yeild じゃなくても良いが、再帰的な関数は慣習としてyeild と呼ばれるらしい

KatEusKatEus

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
KatEusKatEus

ループが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 のループが続いているのが嫌。

KatEusKatEus

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 が返ってくる

KatEusKatEus

つまり、range over func を使うための関数は、false が返ってきた時の処理を考えておく必要がある。
false が返されるのはrange が履行されずにbreak された時。

break されればその後ループは実行されない。もちろんrange over func の中身も実行されない

KatEusKatEus

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
KatEusKatEus

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())
KatEusKatEus
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 型になった