Go: abstract iterative
Suppose I have a method that must either return chan
or slice
. For example, I need chan
if I want to "watch" a file when new lines appear, and a slice if I just want to read and return existing lines.
In both cases, I will need to iterate over this return value. Here's an abstract example in Python (which has nothing to do with files, but seems to show the idea):
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)
Now I find it hard to do this in Go. I suppose I need to look for something like an "iterable interface" that I should return, but I haven't been able to find out-of-the-box solutions (sorry if this is just my poor Google skills).
What is the best way to do what I want? Or maybe the whole design is "bad" for Go and I should consider something else?
Or maybe the whole design is "bad" for Go and I should consider something else?
As long as you can create some kind of interface on top of the types so that you can deal with them as if they were the same, I would say this is a bad choice. It's much easier to use multiple return types and define your function with chan myType, []myType, error
so that it returns, then just use a 3-way if-else to check for an error and then chan or slice. Read about the channel as usual, iterate over the cut as usual. Place the code that works for myType
in a helper method so you can call it from both threads of control.
My money says it is no longer a code, and also much more direct. I don't have to read some abstraction to realize that I have a pipe and inherited complications that come along with it (chan and slice are irrelevant, so they try to simulate them with the same sounds as a nightmare), instead have you just have an extra step in the program control flow.
I'm a bit late to the party, but if you really want "abstract repetition" you can create an interface like this:
type Iterable interface {
Next() (int, error)
}
(Inspired sql.Rows
.)
Then you can use it like this:
for n, err := iter.Next(); err != nil; n, err = iter.Next() {
fmt.Println(n)
}
For iteration, I usually follow the pattern found in sql.Rows and bufio.Scanner . Both have the following equivalent function, returning a bool, indicating whether the next item was successfully retrieved. Then there is a separate value and error accessor method. This pattern allows you to write very clean loops for
without complex conditions (and without using the break
or operators continue
) and moves error handling outside of the loop.
If you want to abstract your string input, you can create, for example, an interface like this:
type LineScanner interface {
Scan() bool
Text() string
Err() error
}
This will give you an abstract source reader as well. As a bonus, using exactly these method names you should make an bufio.Scanner
instant implementation of your interface so you can use it alongside your own types, like the tail-like reader mentioned in your question.
A more complete example:
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