gitalk icon indicating copy to clipboard operation
gitalk copied to clipboard

遍历函数?Go 1.22中隐藏的功能

Open utterances-bot opened this issue 2 years ago • 2 comments

遍历函数?Go 1.22中隐藏的功能

Go 1.22中可以 range 一个整数,比如下面的代码: 123for i := range 10 { fmt.Println(i)}

这个大家都已经知道了,其实对应的提案中还有一个隐藏的功能,就是可以 range 一个函数,比如下面的代码(摘自官方代码库internal/trace/v2/event.go): 12345678910111213141516171819// Frame

https://colobu.com/2024/01/18/range-over-func/

utterances-bot avatar Jan 19 '24 02:01 utterances-bot

这个写法有点难理解

xmx avatar Jan 19 '24 02:01 xmx

我的理解:

  • fn 姑且称为 “迭代函数”,它的参数 yield 姑且称为 “执行函数”,yield 这个名称随意,这里只是为了和 JS 中的迭代器一致。

  • 传递给执行函数的参数,就是最终 range 循环的参数,“执行函数” 每执行一次,range 迭代就会执行一次。

  • 迭代函数控制整个迭代周期,它结束,表示迭代结束,但是也有一种可能会提前 return ,那就是在 range 中使用了 break 或 return

  • 如果 range 中断(break/return),那么迭代函数中的执行函数就会返回 false

  • 此时,执行函数就不能再次执行了,否则 panic ,这就是为什么要在迭代函数中判断,如果执行函数返回 false 则 return ,整个迭代过程结束

      panic: runtime error: range function continued iteration after function for loop body returned false
      // range 函数在 for 循环体返回 false 后继续迭代
    

示例:

package main

import (
	"fmt"
	"time"
)

func main() {
	var fn = func(yield func(k, v any) bool) {
		count := 1
		for {
			time.Sleep(time.Millisecond * 500)
			r := yield(count, time.Now().Format(time.DateTime))
			fmt.Printf("%d continue=%v\n", count, r)
			if !r {
				break
			}
			count++
		}
		fmt.Println("迭代结束")
	}
	for k, v := range fn {
		fmt.Printf("{%v: %v}\n", k, v)
		if k == 2 {
			continue
		}
		if k == 3 {
			fmt.Println("break/return")
			return
		}
	}
}

输出:

{1: 2024-08-14 11:01:51}
1 continue=true
{2: 2024-08-14 11:01:52}
2 continue=true
{3: 2024-08-14 11:01:52}
break/return
3 continue=false
迭代结束

KevinBlandy avatar Aug 14 '24 03:08 KevinBlandy