Go:抽象可迭代
Go: abstract iterable
假设我想要一个方法应该或者return一个chan
或者一个slice
。例如,如果我想在新行出现时 "follow" 一个文件,我需要一个 chan
,如果我只想读取和 return 现有行,我需要一个切片。
在这两种情况下,我只需要遍历这个 return 值。这是 Python 中的一个抽象示例(它与文件无关,但有点展示了这个想法):
def get_iterable(self):
if self.some_flag:
return (x for x in self.some_iterable)
return [x for x in self.some_iterable]
def do_stuff(self):
items = self.get_iterable()
for item in items:
self.process(item)
现在,我在 Go 中很难做到这一点。我想我应该寻找类似 "iterable interface" 的东西,我应该 return,但我没能 google 找到一些现成的解决方案(抱歉,如果这只是我糟糕的谷歌搜索技能) .
做我想做的事最好的方法是什么?或者,也许,整个设计是 "bad" 为 Go 而我应该考虑别的东西?
Or, maybe, the whole design is "bad" for Go and I should consider something else?
虽然您可以在类型之上构建一些接口,以便您可以像处理它们一样处理它们,但我认为这是一个糟糕的选择。更简单的方法是利用多个 return 类型并使用 chan myType, []myType, error
定义函数,因为它是 return 然后只需使用 3 种方式 if-else 检查错误,然后是 chan 或片。像往常一样读取通道,像往常一样迭代切片。将在 myType
上运行的代码放在辅助方法中,以便您可以从两个控制流中调用它。
我的钱说这不再是代码,而且也更直接。我不必通过一些抽象来理解我有一个通道和随之而来的继承复杂性(chan 和 slice 是不协调的,所以试图对它们进行建模听起来像一场噩梦),而你只是有程序控制流程中的一个额外步骤。
我有点晚了,但如果你真的需要一些 "abstract iterable",你可以创建一个这样的界面:
type Iterable interface {
Next() (int, error)
}
(灵感来自 sql.Rows
。)
那么,你可以这样使用它:
for n, err := iter.Next(); err != nil; n, err = iter.Next() {
fmt.Println(n)
}
对于迭代,我通常遵循 sql.Rows and bufio.Scanner 中的模式。两者都有一个 next-equivalent 函数返回 bool,指示是否已成功获取下一个项目。然后有一个单独的方法来访问值和错误。此模式让您可以编写非常干净的 for
循环,无需复杂的条件(并且无需使用 break
或 continue
语句)并将错误处理移到循环之外。
如果你要抽象你的线路输入,你可以创建一个这样的接口:
type LineScanner interface {
Scan() bool
Text() string
Err() error
}
这会给你和摘要行来源reader。作为奖励,通过完全使用这些方法名称,您可以 bufio.Scanner
立即实现您的接口,因此您可以将它与您自己的类型一起使用,例如您的问题中提到的类似尾部的 reader。
更完整的例子:
package main
import (
"bufio"
"fmt"
"strings"
)
type LineScanner interface {
Scan() bool
Text() string
Err() error
}
func main() {
var lr LineScanner
// Use scanner from bufio package
lr = bufio.NewScanner(strings.NewReader("one\ntwo\nthree!\n"))
// Alternatively you can provide your own implementation of LineScanner,
// for example tail-like, blocking on Scan() until next line appears.
// Very clean for loop, isn't it?
for lr.Scan() {
// Handle next line
fmt.Println(lr.Text())
}
// Check if no error while reading
if lr.Err() != nil {
fmt.Println("Error:", lr.Err())
}
}
假设我想要一个方法应该或者return一个chan
或者一个slice
。例如,如果我想在新行出现时 "follow" 一个文件,我需要一个 chan
,如果我只想读取和 return 现有行,我需要一个切片。
在这两种情况下,我只需要遍历这个 return 值。这是 Python 中的一个抽象示例(它与文件无关,但有点展示了这个想法):
def get_iterable(self):
if self.some_flag:
return (x for x in self.some_iterable)
return [x for x in self.some_iterable]
def do_stuff(self):
items = self.get_iterable()
for item in items:
self.process(item)
现在,我在 Go 中很难做到这一点。我想我应该寻找类似 "iterable interface" 的东西,我应该 return,但我没能 google 找到一些现成的解决方案(抱歉,如果这只是我糟糕的谷歌搜索技能) .
做我想做的事最好的方法是什么?或者,也许,整个设计是 "bad" 为 Go 而我应该考虑别的东西?
Or, maybe, the whole design is "bad" for Go and I should consider something else?
虽然您可以在类型之上构建一些接口,以便您可以像处理它们一样处理它们,但我认为这是一个糟糕的选择。更简单的方法是利用多个 return 类型并使用 chan myType, []myType, error
定义函数,因为它是 return 然后只需使用 3 种方式 if-else 检查错误,然后是 chan 或片。像往常一样读取通道,像往常一样迭代切片。将在 myType
上运行的代码放在辅助方法中,以便您可以从两个控制流中调用它。
我的钱说这不再是代码,而且也更直接。我不必通过一些抽象来理解我有一个通道和随之而来的继承复杂性(chan 和 slice 是不协调的,所以试图对它们进行建模听起来像一场噩梦),而你只是有程序控制流程中的一个额外步骤。
我有点晚了,但如果你真的需要一些 "abstract iterable",你可以创建一个这样的界面:
type Iterable interface {
Next() (int, error)
}
(灵感来自 sql.Rows
。)
那么,你可以这样使用它:
for n, err := iter.Next(); err != nil; n, err = iter.Next() {
fmt.Println(n)
}
对于迭代,我通常遵循 sql.Rows and bufio.Scanner 中的模式。两者都有一个 next-equivalent 函数返回 bool,指示是否已成功获取下一个项目。然后有一个单独的方法来访问值和错误。此模式让您可以编写非常干净的 for
循环,无需复杂的条件(并且无需使用 break
或 continue
语句)并将错误处理移到循环之外。
如果你要抽象你的线路输入,你可以创建一个这样的接口:
type LineScanner interface {
Scan() bool
Text() string
Err() error
}
这会给你和摘要行来源reader。作为奖励,通过完全使用这些方法名称,您可以 bufio.Scanner
立即实现您的接口,因此您可以将它与您自己的类型一起使用,例如您的问题中提到的类似尾部的 reader。
更完整的例子:
package main
import (
"bufio"
"fmt"
"strings"
)
type LineScanner interface {
Scan() bool
Text() string
Err() error
}
func main() {
var lr LineScanner
// Use scanner from bufio package
lr = bufio.NewScanner(strings.NewReader("one\ntwo\nthree!\n"))
// Alternatively you can provide your own implementation of LineScanner,
// for example tail-like, blocking on Scan() until next line appears.
// Very clean for loop, isn't it?
for lr.Scan() {
// Handle next line
fmt.Println(lr.Text())
}
// Check if no error while reading
if lr.Err() != nil {
fmt.Println("Error:", lr.Err())
}
}