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?

+3


source to share


3 answers


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.

+3


source


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

      

+2


source


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

0


source







All Articles