内部イテレータを外部イテレータに変形する(その1:配列の場合)再考

木構造で考える前に、もうちょっと配列で考えてみる。(元ネタ:「http://d.hatena.ne.jp/terazzo/20090308/1236526403」)
内部イテレータを外部イテレータに変形する(その1:配列の場合) - チキン煮込みチーズミックス4辛」の最後らへんでちらっと書いたけど、

イテレータは黙って全要素を列挙しとけ。勝手に中断すんな。

という方針に従うと、

  • ForEachで各要素に対して実行される手続きの戻り値が不要になる。
  • ResultHandlerのputResultが不要になる。
  • ResultHandlerは継続を保持するというより、再帰が一気に済んでしまわないように堰き止める役割を持つ。
  • ForEachRecCpsItrerable自体も戻り値が不要になる。

となるので、これに従って以前書いたIntArrayとそのイテレータを書きなおしてみると次のようになった*1

using System;
using System.Collections.Generic;
 
namespace OutIteratorFromInIterator
{
  interface IIterableIn<T>
  {
    void ForEach(IProcedure<T> proc);
  }
 
  interface IProcedure<T>
  {
    void Process(T obj);
  }
 
  class Procedure<T> : IProcedure<T>
  {
    Action<T> _action;
 
    public Procedure(Action<T> action)
    {
      _action = action;
    }
 
    public void Process(T obj)
    {
      _action(obj);
    }
  }
 
  interface IProcedureWithMemory<T> : IProcedure<T>
  {
    T LastProcessedValue();
  }
 
  class ProcedureWithMemory<T> : IProcedureWithMemory<T>
  {
    T _lastProcessedValue;
 
    public void Process(T obj)
    {
      _lastProcessedValue = obj;
    }
 
    public T LastProcessedValue()
    {
      return _lastProcessedValue;
    }
  }
 
  interface INextJobSetter
  {
    void SetNext(Action next);
  }
 
  class NextJobSetter : INextJobSetter
  {
    INextJobSetter _nextJob;
 
    public NextJobSetter(INextJobSetter nextJob)
    {
      _nextJob = nextJob;
    }
 
    public void SetNext(Action next)
    {
      _nextJob.SetNext(next);
    }
  }
 
  interface INextJobAccessor : INextJobSetter
  {
    Action GetNext();
  }
 
  class NextJobAccessor : INextJobAccessor
  {
    Action _next;
 
    public void SetNext(Action next)
    {
      _next = next;
    }
 
    public Action GetNext()
    {
      return _next;
    }
  }
 
  class IntArray : IIterableIn<int>, IEnumerable<int>
  {
    int[] _ar;
 
    public IntArray(int[] ar)
    {
      _ar = ar;
    }
 
    public int this[int index]
    {
      get { return _ar[index]; }
      set { _ar[index] = value; }
    }
 
    public int Count
    {
      get { return _ar.Length; }
    }
 
    public void ForEach(IProcedure<int> proc)
    {
      INextJobAccessor nextJob = new NextJobAccessor();
      ForEachRecCpsIterable(0, this, proc, nextJob);
      Action thunk = null;
      while (null != (thunk = nextJob.GetNext()))
      {
        thunk();
      }
    }
 
    static void ForEachRecCpsIterable(int i, IntArray intArray, IProcedure<int> proc, INextJobSetter nextJob)
    {
      if (i >= intArray.Count)
      {
        nextJob.SetNext(null);
      }
      else
      {
        proc.Process(intArray[i]);
 
        if (intArray.Count <= i + 1)
        {
          nextJob.SetNext(null);
        }
        else
        {
          Action thunk =
            () => ForEachRecCpsIterable(
              i + 1,
              intArray,
              proc,
              new NextJobSetter(nextJob));
          nextJob.SetNext(thunk);
        }
      }
    }
 
    public IEnumerator<int> GetEnumerator()
    {
      IProcedureWithMemory<int> proc = new ProcedureWithMemory<int>();
      INextJobAccessor nextJob = new NextJobAccessor();
 
      Action init =
        Count > 0 ?
        (Action)(() => ForEachRecCpsIterable(0, this, proc, nextJob)) :
        (Action)null;
      nextJob.SetNext(init);
 
      return new IntArrayIterator(nextJob, proc, init);
    }
 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return GetEnumerator();
    }
  }
 
  class IntArrayIterator : IEnumerator<int>
  {
    INextJobAccessor _nextJob;
 
    IProcedureWithMemory<int> _proc;
 
    Action _initCont;
 
    public IntArrayIterator(INextJobAccessor nextJob, IProcedureWithMemory<int> proc, Action initCont)
    {
      _nextJob = nextJob;
      _proc = proc;
      _initCont = initCont;
      Reset();
    }
 
    public int Current
    {
      get
      {
        CheckDisposed();
        return _proc.LastProcessedValue();
      }
    }
 
    public void Dispose()
    {
      CheckDisposed();
      _nextJob = null;
      _initCont = null;
      _proc = null;
    }
 
    object System.Collections.IEnumerator.Current
    {
      get { return Current; }
    }
 
    public bool MoveNext()
    {
      CheckDisposed();
 
      if (_nextJob.GetNext() == null) return false;
      _nextJob.GetNext()();
      return true;
    }
 
    public void Reset()
    {
      CheckDisposed();
      _nextJob.SetNext(_initCont);
    }
 
    void CheckDisposed()
    {
      if (_nextJob == null ||
        _proc == null)
      {
        throw new InvalidOperationException();
      }
    }
  }
 
  class Program
  {
    static void Main(string[] args)
    {
      int[] sample = new int[] { 0, 1, 2, 3, 4 };
      IntArray intArray = new IntArray(sample);
      RunIteratorIn(intArray);
      RunIteratorOut(intArray);
    }
 
    static void RunIteratorIn(IntArray intArray)
    {
      IProcedure<int> proc =
        new Procedure<int>(n => Console.Write(n + " "));
      intArray.ForEach(proc);
      Console.WriteLine("");
    }
 
    static void RunIteratorOut(IntArray intArray)
    {
      foreach (int n in intArray)
      {
        Console.Write(n + " ");
      }
      Console.WriteLine("");
    }
  }
}

実際に動かしてみた:http://ideone.com/Qqj8W

以上のように簡略化すると、外部イテレータとして動く仕組みが見えやすくなると思う。

  • ForEachRecCpsIterableが断続的に繰り返される。
  • ForEachRecCpsIterable内部で実行(Process)されるIProcedureWithMemoryは、処理した値を一時的に記憶しているので、GetLastProcessedValue()で取り出す。

がうまいこと協調してイテレータとして動いているようだ。なるほどねー。

*1:ジェネリッククラスにしたり、クラス名を変更したり、若干変更は加えている。