The last few weeks I’ve been talking about different iterator patterns, but from the perspective of consuming iterators. Let’s switch angles now, and begin talking about how to implement iterators.
To illustrate, let’s use a simple example of a grep utility. It will read an io.Reader, and return any lines that match a regular expression input. Here’s a simple implementation, that returns all results at once (no iteration):
func grep(r io.Reader, pattern string) ([]string, error) {
re, err := regexp.Parse(pattern)
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(r)
var matches []string
for scanner.Scan() {
line := scanner.Text()
if re.MatchString(line) {
matches = append(matches, line)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return matches, nil
}
And as expected, this works fine for small inputs, but would not be ideal for large inputs, or if you want to stop processing after the first N results, for example… this is where iterators are quite useful, and we’ll be looking at re-implementing this using different iterator patterns for the rest of this series.