木構造で考える前に、もうちょっと配列で考えてみる。(元ネタ:「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()で取り出す。
がうまいこと協調してイテレータとして動いているようだ。なるほどねー。