英文:
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 "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)
}
}
答案1
得分: 6
我不认为有办法避免检查ok,但你可以重新构造它以避免丑陋的break:
for v, ok := f(); ok; v, ok = f() {
fmt.Println(v)
}
需要注意的是,这只适用于以下情况之一:
-
你有一个具有多个返回值需要检查的函数,或者
-
你有一个或多个只有一个返回值需要检查的函数
不幸的是,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:
-
You have a single function with multiple return values to check, OR
-
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 && 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<- 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)
}
}
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() <-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)
}
}
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 (
"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)
}
}
Output:
line1
line2
Your example looks idiomatic to me.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论