refactor IterExplorer to allow for more flexible strategy definition

This commit is contained in:
Fischlurch 2012-06-03 20:26:43 +02:00
parent dc3ebd4a8f
commit da4a343e9e
2 changed files with 131 additions and 42 deletions

View file

@ -22,7 +22,7 @@
/** @file iter-explorer.hpp
** Helper template(s) for establishing various evaluation strategies for hierarchical data structures.
** Based on the <b>Lumiera Forward Iterators</b> concept and using the basic IterAdaptor templates,
** Based on the <b>Lumiera Forward Iterator</b> 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 SRC
,template<class,class> 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 SRC, class FUN>
class DefaultCombinator
template<class SIG>
struct ExploreByFunction
: function<SIG>
{
typedef typename _Fun<FUN>::Ret ResultIter;
typedef typename SRC::value_type SrcElement;
typedef function<ResultIter(SrcElement)> Explorer;
template<typename FUN>
ExploreByFunction(FUN explorationFunctionDefinition)
: function<SIG>(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<class SIG>
struct UnalteredPassThrough;
template<class IT>
struct UnalteredPassThrough<IT(IT)>
{
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 SRC, class FUN
,template<class> class _EXP_ = ExploreByFunction ///< Strategy: how to store and evaluate the function to apply on each element
>
class CombinedIteratorEvaluation
{
typedef typename _Fun<FUN>::Ret ResultIter;
typedef typename SRC::value_type SrcElement;
typedef _EXP_<ResultIter(SrcElement)> 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 SRC, class FUN>
class DefaultCombinator
: public CombinedIteratorEvaluation<SRC,FUN>
{
typedef typename _Fun<FUN>::Ret ResultIter;
public:
DefaultCombinator() { }
DefaultCombinator(FUN explorerFunction)
: CombinedIteratorEvaluation<SRC,FUN>(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 SEQ>
class ChainedIters
{
/////////////TODO unimplemented
};
/**
* Helper template to bootstrap a chain of IterExplorers.

View file

@ -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<NumberSequence> 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<NumberSequence> 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");