iterable と iterator

イテレータを介して見るPHPクラスの内部構造 を読んだのですが、 最近の php はちゃんと Python などの言語を参考にしてるなーと思うことが多かったのにこの仕様は残念です。

Python など多くの言語では、イテレータの取得とイテレータの使用を明確に区別しています。 Python ではイテレータを取得できるオブジェクトを iterable と呼び、イテレータiterator と呼びます。

iterable は __iter__ メソッドを実装し、 iterator__next__ メソッド (Python 2 では next)を実装します。 また、 iterator は同時に iterable も実装し、自分自身を返すようにします。 これにより、 for 文などは「イテレータを取得しそれをイテレートする」というシンプルな動作ができます。

iterable と iterator を愚直に実装すると次のようになります。

class Foo(object):
    def __init__(self, L):
        self._L = L

    def __iter__(self):
        return FooIterator(self)

class FooIterator(object):
    def __init__(self, foo):
        self._i = 0
        self._foo = foo

    def __iter__(self):
        return self

    def __next__(self):
        try:
            v = self._foo._L[self._i]
            self._i += 1
            return v
        except IndexError:
            raise StopIteration

    next = __next__  # For Python 2 compatibility.

使ってみましょう。

>>> foo = Foo([1,2,3])
>>> ifoo = iter(foo)  # イテレータの取得
>>> next(ifoo)        # その巡回
1
>>> for v in foo:     # イテレータの取得と巡回
...     print(v)
... 
1
2
3
>>> for v in ifoo:    # イテレータからイテレータを取得するとそれ自体が返るので、「続き」を for ループできる
...     print(v)
... 
2
3

iterator と iterable の違いが判っていれば、とても素直な実装に見えるでしょう。

さて、 Python のジェネレータは同時に iterator です。なので、先ほどのコードはこう書けます。

class Foo(object):
    def __init__(self, L):
        self._L = L

    def __iter__(self):
        for v in self._L:
            yield v

もちろん、 __iter__iterator を返せばいいのですから、次のように書いてもOKです。

class Foo(object):
    def __init__(self, L):
        self._L = L

    def __iter__(self):
        return iter(self._L)

php__getIterator() メソッドとか用意して、ジェネレータなどを使って簡単にイテレータを返せるようになると便利になると思います。

(追記) ちゃんと php にも IteratorAggregate がありました。 問題はAPI設計ではなく、 SplQueue などが使ってる C 言語レベルのインタフェースと IteratorAggregate インタフェースがリンクしていないという オブジェクトシステムの欠陥のようです。

phpイテレータを実装するときは、直接実装するのではなくまず IteratorAggregate の使用を検討しましょう。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。