在Go语言中,以惯用方式轮询函数直到ok != true。

huangapple go评论86阅读模式
英文:

idiomatic way to poll function until ok != true in go

问题

我有一个函数,在轮询时返回值,但在某个时刻将停止返回有意义的值,如下所示。

有没有比每次检查if !ok更符合惯用方式的轮询方法。我在想是否可以类似使用range来轮询通道。

package main

import "fmt"

func iter() func() (int, bool) {
    i := 0
    return func() (int, bool) {
        if i < 10 {
            i++
            return i, true
        }
        return i, false
    }
}

func main() {
    f := iter()
    for {
        v, ok := f()
        if !ok {
            break
        }
        fmt.Println(v)
    }
}
英文:

I have a function which returns values when polled, but then at some point will stop returning sensible values as shown below.

Is there a more idiomatic way to poll it than checking if !ok every time. I'm thinking something akin to polling a channel with range.

package main

import &quot;fmt&quot;

func iter() func() (int, bool) {
    i := 0
	    return func() (int, bool) {
	    if i &lt; 10 {
		    i++
		    return i, true
	    }
	    return i, false
    }
}

func main() {
    f := iter()
    for {
	    v, ok := f()
	    if !ok {
		    break
	    }
	    fmt.Println(v)
    }
}

答案1

得分: 6

我不认为有办法避免检查ok,但你可以重新构造它以避免丑陋的break:

for v, ok := f(); ok; v, ok = f() {
    fmt.Println(v)
}

需要注意的是,这只适用于以下情况之一:

  1. 你有一个具有多个返回值需要检查的函数,或者

  2. 你有一个或多个只有一个返回值需要检查的函数

不幸的是,Go不允许你这样做:

f := iter()
g := iter()
v, ok, v2, ok2 := f(), g(); ok && ok2; v, ok, v2, ok2 = f(), g() {
   // 代码
}

所以如果你有多个函数的情况,除非它们只返回一个值,否则你只能使用if和break。

话虽如此(经过反思),在Go中编写迭代器的更典型方式是使用通道进行迭代。考虑下面的等效程序:

func Iterator(iterCh chan<- int) {
    for i := 0; i < 10; i++ {
       iterCh <- i
    }
    close(iterCh)
}

func main() {
    iter := make(chan int)
    go Iterator(iter)
    for v := range iter {
       fmt.Println(v)
    }
}

在这种情况下,不再返回布尔值,只需在发送完值后关闭通道即可。这种方法的缺点是,如果你想返回多个值,你必须创建一个适当的结构体来发送到通道中。

最后,如果你想稍微封装一下,以避免每次运行迭代器时都要写通道的样板代码:

func Iter() <-chan int {
   iterChan := make(chan int)
   go iter(iterChan)
   return iterChan
}
func iter(iterCh chan<- int) {
    for i := 0; i < 10; i++ {
       iterCh <- i
    }
    close(iterCh)
}

func main() {
    for v := range Iter() {
       fmt.Println(v)
    }
}

这种方式的初始实现需要更多的代码,但可以避免每次使用迭代器时手动声明一个通道。

英文:

I don't think there's a way to avoid checking ok, but you can restructure it to avoid the ugly break:

for v,ok := f(); ok; v,ok = f() {
    fmt.Println(v)
}

It should be noted that this only works in cases where either:

  1. You have a single function with multiple return values to check, OR

  2. You have one or more functions with only one return value to check

Unfortunately Go won't let you do things like

f := iter()
g := iter()
v,ok,v2,ok2 := f(), g(); ok &amp;&amp; ok2; v,ok,v2,ok2 := f(), g() {
   // code
}

So if you have a case with multiple functions you're stuck with ifs and breaks unless they only return a single value.

That said, (and on reflection), the more idiomatic way to write an iterator in Go is ranging over a channel. Consider the equivalent program:

func Iterator(iterCh chan&lt;- int) {
    for i := 0; i &lt; 10; i++ {
       iterCh &lt;- i
    }
    close(iterCh)
}

func main() {
    iter := make(chan int)
    go Iterator(iter)
    for v := range iter {
       fmt.Println(v)
    }
}

In this case, instead of returning a boolean value, just close the channel whenever you're done sending values. The downside of this method is that if you want to return multiple values, you have to make a struct of some sort to send over the channel.

And finally, if you want to wrap it a bit to hide the channel boilerplate every time you run your iterator:

func Iter() &lt;-chan int {
   iterChan := make(chan int)
   go iter(iterChan)
   return iterChan
}
func iter(iterCh chan&lt;- int) {
    for i := 0; i &lt; 10; i++ {
       iterCh &lt;- i
    }
    close(iterCh)
}

func main() {
    for v := range Iter() {
       fmt.Println(v)
    }
}

Which is more code for the initial implementation, but removes having to manually declare a channel every time you want to use an iterator.

答案2

得分: 1

我不认为你的例子与读取到文件末尾的常见习语有太大的区别。例如,

package main

import (
	"bytes"
	"fmt"
	"io"
	"strings"
)

func main() {
	buf := bytes.NewBufferString("line1\nline2")
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
				return
			}
			if len(line) == 0 {
				break
			}
		}
		line = strings.TrimSuffix(line, "\n")
		fmt.Println(line)
	}
}

输出:

line1
line2

我认为你的例子很符合习惯用法。

英文:

I don't see how your example is much different from the common idiom for reading until the end of file. For example,

package main

import (
	&quot;bytes&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;strings&quot;
)

func main() {
	buf := bytes.NewBufferString(&quot;line1\nline2&quot;)
	for {
		line, err := buf.ReadString(&#39;\n&#39;)
		if err != nil {
			if err != io.EOF {
				fmt.Println(err)
				return
			}
			if len(line) == 0 {
				break
			}
		}
		line = strings.TrimSuffix(line, &quot;\n&quot;)
		fmt.Println(line)
	}
}

Output:

line1
line2

Your example looks idiomatic to me.

huangapple
  • 本文由 发表于 2013年7月26日 03:42:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/17867305.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定