From da4a343e9e2bbc584a1f0471b596072ce4570e1b Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 3 Jun 2012 20:26:43 +0200 Subject: [PATCH] refactor IterExplorer to allow for more flexible strategy definition --- src/lib/iter-explorer.hpp | 151 ++++++++++++++++++++++++------- tests/lib/iter-explorer-test.cpp | 22 ++--- 2 files changed, 131 insertions(+), 42 deletions(-) diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index ffc5865bc..a9a939419 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -22,7 +22,7 @@ /** @file iter-explorer.hpp ** Helper template(s) for establishing various evaluation strategies for hierarchical data structures. - ** Based on the Lumiera Forward Iterators concept and using the basic IterAdaptor templates, + ** Based on the Lumiera Forward Iterator concept and using the basic IterAdaptor templates, ** these components allow to implement typical evaluation strategies, like e.g. depth-first or ** breadth-first exploration of a hierarchical structure. Since the access to this structure is ** abstracted through the underlying iterator, what we effectively get is a functional datastructure. @@ -32,10 +32,10 @@ ** \par Iterators as Monad ** The fundamental idea behind the implementation technique used here is the \em Monad pattern ** known from functional programming. A Monad is a (abstract) container created by using some specific functions. - ** This is an quite abstract concept with a wide variety of applications (things like IO state, parsers, combinators, - ** calculations with exception handling but also simple data structures like lists or trees). The key point with - ** any monad is the ability to \em bind a function into the monad; this function will work on the \em internals - ** of the monad and produce a modified new monad instance. In the simple case of a list, "binding" a function + ** This is an rather abstract concept with a wide variety of applications (things like IO state, parsers, combinators, + ** calculations with exception handling but also simple data structures like lists or trees). The key point with any + ** monad is the ability to \em bind a function into the monad; this function will work on the \em internals of the + ** monad and produce a modified new monad instance. In the simple case of a list, "binding" a function ** basically means to map the function onto the elements in the list. ** ** \par Rationale @@ -168,6 +168,12 @@ namespace lib { * independent, new result sequence based on this first element). * Afterwards, the source is \em advanced and then \em copied * into the result iterator. + * @param SRC the source sequence or iterator to wrap + * @param _COM_ "Combinator" strategy template. When binding (\c >>= ) a function, + * an instance of that strategy becomes the new SRC for the created new + * IterExplorer. This instantiation of the strategy gets as type parameters + * - this IterExplorer's instance type + * - the type of the function bound with \c >>= */ template class _COM_ = iter_explorer::DefaultCombinator @@ -187,7 +193,7 @@ namespace lib { IterExplorer() { } - /** wrap an iterator like state representation + /** wrap an iterator-like state representation * to build it into a monad. The resulting entity * is both an iterator yielding the elements generated * by the core, and it provides the (monad) bind operator. @@ -247,29 +253,64 @@ namespace lib { namespace iter_explorer { ///< predefined policies and configurations using util::unConst; - + /** - * a generic "Combinator strategy" for IterExplorer. - * This fallback solution doesn't assume anything beyond the source - * and the intermediary result(s) being a Lumiera Forward Iterators. - * @note the implementation stores the functor into a std::function object, - * which might cause heap allocations, depending on given function. - * Besides, the implementation holds one instance of the (intermediary) - * result iterator (yielded by invoking the function) and a copy of the - * original IterExplorer source sequence, to get the further elements - * when the initial results are exhausted. + * Building block: evaluating source elements. + * This strategy will be tied into a "Combinator" + * to hold the actual functor bound into the enclosing + * IterExplorer monad to work on the contained elements. */ - template - class DefaultCombinator + template + struct ExploreByFunction + : function { - typedef typename _Fun::Ret ResultIter; - typedef typename SRC::value_type SrcElement; - typedef function Explorer; + template + ExploreByFunction(FUN explorationFunctionDefinition) + : function(explorationFunctionDefinition) + { } - - SRC srcSeq_; + ExploreByFunction() { } ///< by default initialised to bottom function + }; + + /** + * Support for a special use case: an Iterator of Iterators, joining results. + * In this case, already the source produces a sequence of Iterators, which + * just need to be passed through to the output buffer unaltered. Using this + * within the DefaultCombinator strategy creates a combined, flattened iterator + * of all the source iterator's contents. + */ + template + struct UnalteredPassThrough; + + template + struct UnalteredPassThrough + { + IT operator() (IT elm) const { return elm; } + }; + + + /** + * Building block: evaluate and combine a sequence of iterators. + * This implementation helper provides two kinds of "buffers" (actually implemented + * as iterators): A result buffer (iterator) which holds a sequence of already prepared + * result elements, which can be retrieved through iteration right away. And a supply buffer + * (iterator) holding raw source elements. When the result buffer is exhausted, the next source + * element will be pulled from there and fed through the "evaluation strategy", which typically + * is a function processing the source element and producing a new result buffer (iterator). + */ + template class _EXP_ = ExploreByFunction ///< Strategy: how to store and evaluate the function to apply on each element + > + class CombinedIteratorEvaluation + { + typedef typename _Fun::Ret ResultIter; + typedef typename SRC::value_type SrcElement; + typedef _EXP_ Explorer; + + + SRC srcSeq_; ResultIter results_; - Explorer explorer_; + Explorer explorer_; public: typedef typename ResultIter::value_type value_type; @@ -277,9 +318,9 @@ namespace lib { typedef typename ResultIter::pointer pointer; - DefaultCombinator() { } + CombinedIteratorEvaluation() { } - DefaultCombinator(FUN explorerFunction) + CombinedIteratorEvaluation(FUN explorerFunction) : srcSeq_() , results_() , explorer_(explorerFunction) @@ -289,13 +330,13 @@ namespace lib { void - startWith (ResultIter firstExplorationResult) + setResultSequence (ResultIter firstExplorationResult) { results_ = firstExplorationResult; } void - followUp (SRC & followUpSourceElements) + setSourceSequence (SRC & followUpSourceElements) { REQUIRE (explorer_); srcSeq_ = followUpSourceElements; @@ -316,24 +357,72 @@ namespace lib { /* === Iteration control API for IterStateWrapper== */ friend bool - checkPoint (DefaultCombinator const& seq) + checkPoint (CombinedIteratorEvaluation const& seq) { return unConst(seq).findNextResultElement(); } friend reference - yield (DefaultCombinator const& seq) + yield (CombinedIteratorEvaluation const& seq) { return *(seq.results_); } friend void - iterNext (DefaultCombinator & seq) + iterNext (CombinedIteratorEvaluation & seq) { ++(seq.results_); } }; + /** + * a generic "Combinator strategy" for IterExplorer. + * This fallback solution doesn't assume anything beyond the source + * and the intermediary result(s) being a Lumiera Forward Iterators. + * @note the implementation stores the functor into a std::function object, + * which might cause heap allocations, depending on given function. + * Besides, the implementation holds one instance of the (intermediary) + * result iterator (yielded by invoking the function) and a copy of the + * original IterExplorer source sequence, to get the further elements + * when the initial results are exhausted. + */ + template + class DefaultCombinator + : public CombinedIteratorEvaluation + { + typedef typename _Fun::Ret ResultIter; + + public: + DefaultCombinator() { } + + DefaultCombinator(FUN explorerFunction) + : CombinedIteratorEvaluation(explorerFunction) + { } + + + void + startWith (ResultIter firstExplorationResult) + { + this->setResultSequence (firstExplorationResult); + } + + void + followUp (SRC & followUpSourceElements) + { + this->setSourceSequence (followUpSourceElements); + } + }; + + + /** + * Special configuration for combining / flattening the results + * of a sequence of iterators + */ + template + class ChainedIters + { + /////////////TODO unimplemented + }; /** * Helper template to bootstrap a chain of IterExplorers. diff --git a/tests/lib/iter-explorer-test.cpp b/tests/lib/iter-explorer-test.cpp index 88001f510..070fbbeca 100644 --- a/tests/lib/iter-explorer-test.cpp +++ b/tests/lib/iter-explorer-test.cpp @@ -61,7 +61,7 @@ namespace test{ /** * This iteration state type describes - * a sequence of numbers still to be delivered. + * a sequence of numbers yet to be delivered. */ class State { @@ -385,10 +385,11 @@ namespace test{ * of a tree like system, built on top of the IterExplorer monad. * * \par Test data structure - * We built a functional datastructure here, on the fly, while exploring it. + * We build a functional datastructure here, on the fly, while exploring it. * The \c exploreChildren(m) function generates this tree like datastructure: * For a given node with number value i, it returns the children (2*i-1) and 2*i * -- but only up to the given maximum value m. + * * If we start such a tree structure with a root node 1, and maximum = 8, this yields: * \code * ( 1 ) @@ -400,9 +401,9 @@ namespace test{ * * \par How the exploration works * We use a pre defined Template \link DepthFirstExplorer \endlink, which is built on top of IterExplorer. - * It contains the depth-first exploration strategy hardwired. Actually this effect is achieved by - * defining a specific way how to \em combine the results of a \em exploration -- the latter being - * the function which generates the data structure. To yield a depth-first exploration, all we have to do + * It contains the depth-first exploration strategy in a hardwired fashion. Actually this effect is achieved + * by defining a specific way how to \em combine the results of an \em exploration -- the latter being the + * function which generates the data structure. To yield a depth-first exploration, all we have to do * is to delve down immediately into the children, right after visiting the node itself. * * Now, when creating such a DepthFirstExplorer by wrapping a given source iterator, the result is again @@ -421,8 +422,8 @@ namespace test{ typedef DepthFirstExplorer DepthFirst; DepthFirst root (seq(1)); - NumberSequence explorationResult = root >>= exploreChildren(8); - CHECK (materialise(explorationResult) == "1-1-1-1-2-2-3-4-2-3-5-6-4-7-8"); + string explorationResult = materialise(root >>= exploreChildren(8)); + CHECK (explorationResult == "1-1-1-1-2-2-3-4-2-3-5-6-4-7-8"); #endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892 } @@ -441,8 +442,8 @@ namespace test{ typedef BreadthFirstExplorer BreadthFirst; BreadthFirst root (seq(1)); - NumberSequence explorationResult = root >>= exploreChildren(8); - CHECK (materialise(explorationResult) == "1-1-2-1-2-3-4-1-2-3-4-5-6-7-8"); + string explorationResult = materialise(root >>= exploreChildren(8)); + CHECK (explorationResult == "1-1-2-1-2-3-4-1-2-3-4-5-6-7-8"); #endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #892 } @@ -504,10 +505,9 @@ namespace test{ { return seq(0,top); } - - }; + LAUNCHER (IterExplorer_test, "unit common");