About chapter 15, building iterator from scratch

About the class HundredSquares, the book use the following code:

class HundredSquares extends Iterable<int> {
  @override
  Iterator<int> get iterator => SquaredIterator();
}

I find that it is also work if I change the return type as follows:

class HundredSquares extends Iterable<int> {   
    @override   
    SquaredIterator get iterator => SquaredIterator(); 
}

What are the difference between the two? If both codes are ok, any considerations on choosing which one to use?

Since SquaredIterator implements Iterator<int>, Dart can treat them the same and they are replaceable. SquaredIterator is a concrete implementation of an abstract Iterator class of generic type int (see Dart Apprentence: Beyond the Basics for more on that).

It doesn’t matter too much which one you use, but your iterator is an implementation detail. Most developers who use your code don’t care about how you made your iterator or what you called it as long as it works. In my opinion, it’s better to use Iterator<int> for readability sake.

For example, say a developer wants to use your iterator. They can get it like so:

final squares = HundredSquares();
final iterator = squares.iterator;

By type inference, Dart displays the type of iterator as Iterator<int>. Any developer who is familiar with Dart understands the meaning of that type. However, if you had made your iterator return the type SquaredIterator, then Dart would infer iterator in the code above to be of type SquaredIterator. Since you defined this name yourself, it isn’t so clear what it means. Does it provide strings or integers or something else? The developer can’t be sure without going to the source code.

So in the end, my summary is, when you have two valid choices, go for the one that is more readable and understandable.

3 Likes

Hi @suragch , thank you for your detailed explanation. Somehow, I also find a subtle difference between the two, see the code below:

class SquaredIterator implements Iterator<int> {
  int _index = 0;
  int _current = 0;

  @override
  bool moveNext() {
    _current = _index * _index;
    _index++;
    return _index <= 100;
  }

  @override
  int get current => _current;

  int get currentIndex => _index;
}

class HundredSquares extends Iterable<int> {
  @override
  SquaredIterator get iterator => SquaredIterator();
}

main() {
  final squares = HundredSquares();
  final iterator = squares.iterator;
  print(iterator.currentIndex);
}

I try to add a getter method in HundredSquares class to access the _index. I find that I must return the type SquaredIterator in order to access the getter. Otherwise it will raise a compile error: The getter ‘currentIndex’ isn’t defined for the type ‘Iterator’.

Not quite know exactly the reason and don’t know whether there is any practical use about this.

When the HundredSquares iterable returns Iterator<int> from the iterator, that Iterator<int> doesn’t know about your custom currentIndex getter, so that’s why you get the error. Since you know that it’s actually a SquaredIterator, you could downcast like the following to remove the error.

final squares = HundredSquares();
final iterator = squares.iterator as SquaredIterator;
print(iterator.currentIndex);

Or just return SquaredIterator directly from your iterator getter as you did in your example above.

Yours is a good example of another implication in the difference between approaches. However, I also don’t know of a practical application for getting the internal _index value. If you don’t need that value, then I would probably still go with the more generic Iterator<int> type.

2 Likes