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 循环,无需复杂的条件(并且无需使用 breakcontinue 语句)并将错误处理移到循环之外。

如果你要抽象你的线路输入,你可以创建一个这样的接口:

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())
    }
}

http://play.golang.org/p/LRbGWj9_Xw