diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 603000d44..e01603342 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -46,49 +46,20 @@ ** 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 + ** ## Rationale ** The primary benefit of using the monad pattern is to separate the transforming operation completely from ** the mechanics of applying that operation and combining the results. More specifically, we rely on an iterator ** to represent an abstracted source of data and we expose the combined and transformed results again as such - ** an abstracted data sequence. The transformation to apply can be selected at runtime (as a functor), and - ** also the logic how to combine elements can be implemented elsewhere. The monad pattern defines a sane - ** way of representing this partial evaluation state without requiring a container for intermediary - ** results. This is especially helpful when + ** an abstracted data sequence. While the transformation to apply can be selected at runtime (as a functor), + ** the monad pattern defines a sane way to represent partial evaluation state without requiring a container + ** for intermediary results. This is especially helpful when ** - a flexible and unspecific source data structure needs to be processed ** - and this evaluation needs to be done asynchronously and in parallel (no locking, immutable data) - ** - and a partial evaluation needs to be stored as continuation (not relying on the stack for partial results) + ** - and a partial evaluation needs to be stored as continuation (not relying on the stack for partial results) ** - ** \par preconfigured solutions - ** This header provides some preconfigured applications of this pattern - ** - the DefaultCombinator processes the source elements on demand, feeding them through - ** the given functor and using the resulting iterator to deliver the result elements - ** - Chained iterator uses similar building blocks just to get the "flattening" of - ** a sequence of iterators into a single result iterator - ** - the RecursiveExhaustingEvaluation is another kind of combination strategy, - ** which recursively evaluates the given function and combines the results - ** such as to produce classical depth-first and breadth-first search orders. - ** - the more low-level RecursiveSelfIntegration combinator strategy actually - ** delegates to the result set iterator implementation to perform the collecting - ** and re-integrating of intermediary results. This approach is what we actually - ** use in the proc::engine::Dispatcher + ** @todo WIP-WIP-WIP initial draft as of 11/2017 ** - ** Alternatively, just the basic IterExplorer template can be used together with a custom - ** "combinator strategy" and typically even a specific iterator or sequence to implement very specific - ** and optimised data structure evaluation patterns. This strategy needs to define some way to hold onto - ** the original source elements, feed them through the functor on demand and recombine the result sets - ** into a new sequence to be delivered on demand. - ** Actually this is what we utilise for the continuous render job generation within the scheduler. - ** All the other preconfigured variants defined here where created as proof-of-concept, to document - ** and verify this implementation technique as such. - ** - ** @warning preferably use value semantics for the elements to be processed. Recall, C++ is not - ** really a functional programming language, and there is no garbage collector. It might be - ** tempting just to pass pointers through a whole evaluation chain. Indeed, you can do so, - ** but make sure you understand the precise timing of the evaluation, expansion and - ** re-integration steps with regards to memory management; an "explorer function" - ** may pass a reference or pointer to some transient source, which is gone after - ** incrementing the source iterator. - ** @see IterExplorer_test + ** @see IterTreeExplorer_test ** @see iter-adapter.hpp ** @see itertools.hpp ** @see IterSource (completely opaque iterator) @@ -116,8 +87,7 @@ namespace lib { namespace iter_explorer { - template - class DefaultCombinator; + ////////////TODO } @@ -131,9 +101,8 @@ namespace lib { * @todo WIP -- preliminary draft as of 11/2017 */ template class _COM_ = iter_explorer::DefaultCombinator > - class IterExplorer + class IterTreeExplorer : public IterStateWrapper { @@ -143,19 +112,9 @@ namespace lib { typedef typename SRC::reference reference; typedef typename SRC::pointer pointer; - /** Metafunction: the resulting type when binding ("flat mapping") - * a functor of type FUN. Basically the result of binding a function - * is again an IterExplorer (with an "expanded" state core type) */ - template - struct FlatMapped - { - typedef IterExplorer<_COM_, _COM_> Type; - }; - - /** by default create an empty iterator */ - IterExplorer() { } + IterTreeExplorer() { } /** wrap an iterator-like state representation @@ -164,41 +123,12 @@ namespace lib { * by the core, and it provides the (monad) bind operator. */ explicit - IterExplorer (SRC const& iterStateCore) + IterTreeExplorer (SRC const& iterStateCore) : IterStateWrapper (iterStateCore) { } - /** monad bind ("flat map") operator. - * Using a specific function to explore and work - * on the "contents" of this IterExplorer, with the goal - * to build a new IterExplorer combining the results of this - * function application. The enclosing IterExplorer instance - * provides a Strategy template _COM_, which defines how those - * results are actually to be combined. An instantiation of - * this "Combinator" strategy becomes the state core - * of the result iterator. - */ - template - typename FlatMapped::Type - operator >>= (FUN explorer) - { - typedef _COM_ Combinator; // instantiation of the combinator strategy - typedef typename FlatMapped::Type ResultsIter; // result IterExplorer using that instance as state core - - return ResultsIter ( - Combinator (explorer // build a new iteration state core - ,accessRemainingElements())); // based on a copy of this iterator / sequence - } - - - private: - IterExplorer const& - accessRemainingElements() - { - return *this; - } }; @@ -210,680 +140,15 @@ namespace lib { - namespace iter_explorer { ///< predefined "exploration strategies", policies and configurations + namespace iter_explorer { + + /////TODO RLY? using util::unConst; using lib::meta::enable_if; using lib::meta::disable_if; using std::function; using meta::_Fun; - - - /** - * Building block: just evaluate 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 - struct ExploreByFunction - : function - { - template - ExploreByFunction(FUN explorationFunctionDefinition) - : function(explorationFunctionDefinition) - { } - - 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; } - bool operator! () const { return false; } ///< identity function is always valid - }; - - - - /** - * 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_; - - public: - typedef typename ResultIter::value_type value_type; - typedef typename ResultIter::reference reference; - typedef typename ResultIter::pointer pointer; - - - CombinedIteratorEvaluation() { } - - CombinedIteratorEvaluation(FUN explorerFunction) - : srcSeq_() - , results_() - , explorer_(explorerFunction) - { } - - // using standard copy operations - - - void - setSourceSequence (SRC const& followUpSourceElements) - { - REQUIRE (explorer_); - srcSeq_ = followUpSourceElements; - } - - private: - bool - findNextResultElement() - { - while (!results_ && srcSeq_) - { - results_ = explorer_(*srcSeq_); - ++srcSeq_; - } - return bool(results_); - - } - /* === Iteration control API for IterStateWrapper== */ - - friend bool - checkPoint (CombinedIteratorEvaluation const& seq) - { - return unConst(seq).findNextResultElement(); - } - - friend reference - yield (CombinedIteratorEvaluation const& seq) - { - return *(seq.results_); - } - - friend void - iterNext (CombinedIteratorEvaluation & seq) - { - ++(seq.results_); - } - }; - - /** - * a generic "Combinator strategy" for IterExplorer. - * This default / fallback solution doesn't assume anything beyond the - * source and the intermediary result(s) to be Lumiera Forward Iterators. - * @note the implementation stores the functor into a std::function object, - * which might cause heap allocations, depending on the function given. - * 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, SRC const& sourceElements) - : CombinedIteratorEvaluation(explorerFunction) - { - this->setSourceSequence (sourceElements); - } - }; - - - /** Metafunction to detect an iterator yielding an iterator sequence */ - template - struct _is_iterator_of_iterators - { - typedef typename IT::value_type IteratorElementType; - - enum{ value = meta::can_IterForEach::value }; - }; - - - template - class ChainedIteratorImpl - : public CombinedIteratorEvaluation - { }; - - - /** - * Special iterator configuration for combining / flattening the - * results of a sequence of iterators. This sequence of source iterators - * is assumed to be available as "Iterator yielding Iterators". - * The resulting class is a Lumiera Forward Iterator, delivering all the - * elements of all source iterators in sequence. - * @remarks this is quite similar to the IterExplorer monad, but without - * binding an exploration function to produce the result sequences. - * Rather, the result sequences are directly pulled from the source - * sequence, which thus needs to be an "Iterator of Iterators". - * Beyond that, the implementation relies on the same building - * blocks as used for the full-blown IterExplorer. - * @param ITI iterator of iterators - * @param SEQ type of the individual sequence (iterator). - * The value_type of this sequence will be the overall - * resulting value type of the flattened sequence - */ - template - class ChainedIters; - - template - class ChainedIters> - > - : public IterStateWrapper - > - { - public: - ChainedIters(ITI const& iteratorOfIterators) - { // note: default ctor on parent -> empty sequences - this->stateCore().setSourceSequence (iteratorOfIterators); - } - }; - - /** - * Convenience specialisation: manage the sequence of iterators automatically. - * @note in this case the \em first template parameter denotes the \em element sequence type; - * we use a IterStack to hold the sequence-of-iterators in heap storage. - * @warning this specialisation will not be picked, if the \em value-type - * of the given iterator is itself an iterator - */ - template - class ChainedIters> - > - : public IterStateWrapper, SEQ> - > - { - public: - typedef IterStack IteratorIterator; - - ChainedIters(IteratorIterator const& iteratorOfIterators) - { - this->stateCore().setSourceSequence (iteratorOfIterators); - } - - /** empty result sequence by default */ - ChainedIters() { } - }; - - - - - - - /** - * A "Combinator strategy" allowing to expand and evaluate a - * (functional) data structure successively and recursively. - * Contrary to the DefaultCombinator, here the explorer is evaluated - * repeatedly, feeding back the results until exhaustion. The concrete - * exploration function needs to embody some kind of termination condition, - * e.g. by returning an empty sequence at some point, otherwise infinite - * recursion might happen. Another consequence of this repeated re-evaluation - * is the requirement of the source sequence's element type to be compatible - * to the result sequence's element type -- we can't \em transform the contents - * of the source sequence into another data type, just explore and expand those - * contents into sub-sequences based on the same data type. (While this contradicts - * the full requirements for building a Monad, we can always work around that kind - * of restriction by producing the element type of the target sequence by implicit - * type conversion) - * - * \par strategy requirements - * To build a concrete combinator a special strategy template is required to define - * the actual implementation logic how to proceed with the evaluation (i.e. how to - * find the feed of the "next elements" and how to re-integrate the results of an - * evaluation step into the already expanded sequence of intermediary results. - * Moreover, this implementation strategy pattern is used as a data buffer - * to hold those intermediary results. Together, this allows to create - * various expansion patterns, e.g. depth-first or breadth-first. - * - \c Strategy::getFeed() accesses the point from where - * to pull the next element to be expanded. This function must not - * yield an empty sequence, \em unless the overall exploration is exhausted - * - \c Strategy::feedBack() re-integrates the results of an expansion step - * - * @warning beware, when tempted to pass elements by reference (or pointer) - * through the explorer function, make sure you really understand the - * working mode of the #iterate function with regards to memory management. - * When ResultIter attempts just to store a pointer, after incrementing - * \c ++feed(), depending on the internals of the actual src iterator, - * this pointer might end up dangling. Recommendation is to let the - * Explorer either take arguments or return results by value (copy). - */ - template class _BUF_ - > - class RecursiveExhaustingEvaluation - { - typedef typename _Fun::Ret ResultIter; - typedef typename _Fun::Sig Sig; - typedef function Explorer; - typedef _BUF_ Buffer; - - Buffer resultBuf_; - Explorer explore_; - - - public: - typedef typename ResultIter::value_type value_type; - typedef typename ResultIter::reference reference; - typedef typename ResultIter::pointer pointer; - - RecursiveExhaustingEvaluation (Explorer fun, SRC const& src) - : resultBuf_() - , explore_(fun) - { - resultBuf_.feedBack( - initEvaluation (src)); - } - - RecursiveExhaustingEvaluation() { }; - - // standard copy operations - - - private: - /** Extension point: build the initial evaluation state - * based on the source sequence (typically an IterExplorer). - * This is a tricky problem, since the source sequence is not - * necessarily assignment compatible to the ResultIter type - * and there is no general method to build a ResultIter. - * The solution is to rely on a "builder trait", which - * needs to be defined alongside with the concrete - * ResultIter type. The actual builder trait will - * be picked up through a free function (ADL). */ - ResultIter - initEvaluation (SRC const& initialElements) - { - ResultIter startSet; - return build(startSet).usingSequence(initialElements); - } // extension point: free function build (...) - - - /** @note \c _BUF_::getFeed is required to yield a non-empty sequence, - * until everything is exhausted. Basically the buffer- and re-integrated - * result sequences can be expected to be pulled until finding the next - * non-empty supply. This is considered hidden internal state and thus - * concealed within this \em const and \em idempotent function. - */ - ResultIter & - feed() const - { - return unConst(this)->resultBuf_.getFeed(); - } - - void - iterate () - { - REQUIRE (feed()); - ResultIter nextStep = explore_(*feed()); - ++ feed(); - resultBuf_.feedBack (nextStep); - } - - - /* === Iteration control API for IterStateWrapper== */ - - friend bool - checkPoint (RecursiveExhaustingEvaluation const& seq) - { - return bool(seq.feed()); - } - - friend reference - yield (RecursiveExhaustingEvaluation const& seq) - { - reference result = *(seq.feed()); - return result; - } - - friend void - iterNext (RecursiveExhaustingEvaluation & seq) - { - seq.iterate(); - } - }; - - - /** - * Strategy building block for recursive exhausting evaluation. - * Allows to create depth-fist or breadth-first evaluation patterns, just - * by using a suitably intermediary storage container to hold the partially - * evaluated iterators created at each evaluation step. Using a stack and - * pushing results will create a depth-first pattern, while using a queue - * will evaluate in layers (breadth-first). In both cases, the next - * evaluation step will happen at the iterator returned by #getFeed. - * @warning uses an empty-iterator marker object to signal exhaustion - * - this marker \c IT() may be re-initialised concurrently - * - accessing this marker during app shutdown might access - * an already defunct object - */ - template< class IT - , template class _QUEUE_ ///< the actual container to use for storage of intermediary results - > - class EvaluationBufferStrategy - { - _QUEUE_ intermediaryResults_; - - - /** @return default constructed (=empty) iterator - * @remarks casting away const is safe here, since all - * you can do with an NIL iterator is to test for emptiness. - */ - IT & - emptySequence() - { - return unConst(NullValue::get()); - } // unsafe during shutdown - - - public: - IT & - getFeed () - { - // fast forward to find the next non-empty result sequence - while (intermediaryResults_ && ! *intermediaryResults_) - ++intermediaryResults_; - - if (intermediaryResults_) - return *intermediaryResults_; - else - return emptySequence(); - } - - void - feedBack (IT const& newEvaluationResults) - { - intermediaryResults_.insert (newEvaluationResults); - } - }; - - - - /** - * concrete strategy for recursive \em depth-first evaluation. - * Using heap allocated storage in a STL Deque (used stack-like) - */ - template - struct DepthFirstEvaluationBuffer - : EvaluationBufferStrategy - { }; - - /** - * concrete strategy for recursive \em breadth-first evaluation. - * Using heap allocated storage in a STL Deque (used queue-like) - */ - template - struct BreadthFirstEvaluationBuffer - : EvaluationBufferStrategy - { }; - - - - /** - * preconfigured IterExplorer "state core" resulting in - * depth-first exhaustive evaluation - */ - template - struct DepthFirstEvaluationCombinator - : RecursiveExhaustingEvaluation - { - DepthFirstEvaluationCombinator() { } - - DepthFirstEvaluationCombinator(FUN explorerFunction, SRC const& sourceElements) - : RecursiveExhaustingEvaluation (explorerFunction,sourceElements) - { } - }; - - /** - * preconfigured IterExplorer "state core" resulting in - * breadth-first exhaustive evaluation - */ - template - struct BreadthFirstEvaluationCombinator - : RecursiveExhaustingEvaluation - { - BreadthFirstEvaluationCombinator() { } - - BreadthFirstEvaluationCombinator(FUN explorerFunction, SRC const& sourceElements) - : RecursiveExhaustingEvaluation (explorerFunction,sourceElements) - { } - }; - - - - - /** - * IterExplorer "state core" for progressively expanding - * an initial result set. This initial set can be conceived to hold the seed - * or starting points of evaluation. Elements are consumed by an iterator, at - * the front. Each element is fed to the "explorer function". This exploration - * returns an expanded result sequence, which is immediately integrated into the - * overall result sequence, followed by further exploration of the then-to-be first - * element of the result sequence. All this exploration is driven on-demand, by - * consuming the result sequence. Exploration will proceed until exhaustion, - * in which case the exploration function will yield an empty result set. - * - * This strategy is intended for use with the IterExplorer -- most prominently - * in use for discovering render prerequisites and creating new render jobs for - * the engine. The RecursiveSelfIntegration strategy is a partially reduced - * and hard-coded variation on the #RecursiveExhaustingEvaluation in depth-first - * configuration. - * This setup works in conjunction with a special result sequence type, - * with the ability to re-integrate results yielded by partial evaluation. - * But the working pattern is more similar to the #CombinedIteratorEvaluation, - * where the focal point of further expansion is always at the front end of - * further items yet to be consumed and expanded; thus the evaluation is - * bound to proceed depth-first (i.e. it is \em not possible to "integrate" - * any intermediary result with the \em whole result set, only with the part - * reachable immediately at the evaluation front) - * - * \par usage considerations - * There needs to be a specific sequence or iterator type, which is used to - * hold the result set(s). This custom type together with the Explorer function - * are performing the actual expansion and re-integration steps. The latter is - * accessed through the free function \c build(sequence) -- which is expected - * to return a "builder trait" for manipulating the element set yielded by - * the custom iterator type returned by the Explorer function. - * - * @param SRC the initial result set sequence; this iterator needs to yield - * values of the special ResultIter type, which are then expanded - * until exhaustion by repeated calls to the Explorer function - * @param FUN the Explorer function of type ResultIter -> ResultIter - * @note the ResultIter type is defined implicitly through the result type - * of the Explorer function. Similarly the result value type - * is defined through the typedef \c ResultIter::value_type - * @warning beware of dangling references; make sure you never pass a - * reference or pointer through the Explorer function unaltered. - * Because some source iterator might expose a reference to a - * transient object just for the purpose of expanding it. - * The very reason of building iterator pipelines is to - * avoid heap allocation and to produce intermediaries - * on demand. So make sure in such a case that there is - * at least one real copy operation in the pipeline. - */ - template - class RecursiveSelfIntegration - { - typedef typename _Fun::Ret ResultIter; - typedef typename _Fun::Sig Sig; - typedef typename SRC::value_type Val; - typedef function Explorer; - - SRC srcSeq_; - ResultIter outSeq_; - Explorer explore_; - - public: - typedef typename ResultIter::value_type value_type; - typedef typename ResultIter::reference reference; - typedef typename ResultIter::pointer pointer; - - RecursiveSelfIntegration (Explorer fun, SRC const& src) - : srcSeq_(src) - , outSeq_() - , explore_(fun) - { } - - RecursiveSelfIntegration() { }; - - // standard copy operations - - - private: - /** ensure the next elements to be processed - * will appear at outSeq_ head. When outSeq_ - * is still empty after this function, - * we're done. - * @note \em not calling this function after - * construction, because the typical user - * of this template will do that implicitly - * through invocation of #checkPoint */ - bool - findNextResultElement() - { - while (!outSeq_ && srcSeq_) - { - Val& nextElement(*srcSeq_); - build(outSeq_).wrapping(nextElement); // extension point: free function build(...).wrapping(...) - ++srcSeq_; - } - return bool(outSeq_); - } - - void - iterate () - { - REQUIRE (outSeq_); - ResultIter nextSteps = explore_(*outSeq_); - ++ outSeq_; - build(outSeq_).usingSequence(nextSteps); // extension point: free function build(...).usingSequence(...) - } - - - /* === Iteration control API for IterStateWrapper== */ - - friend bool - checkPoint (RecursiveSelfIntegration const& seq) - { - return unConst(seq).findNextResultElement(); - } - - friend reference - yield (RecursiveSelfIntegration const& seq) - { - return *(seq.outSeq_); - } - - friend void - iterNext (RecursiveSelfIntegration & seq) - { - seq.iterate(); - } - }; - - - - - /** - * Helper template to bootstrap a chain of IterExplorers. - * This is a "state core", which basically just wraps a given - * source iterator and provides the necessary free functions - * (iteration control API) to use this as iteration state - * within IterExplorer. - * @note to ease building such an initial version of the Iterator Monad, - * use the free function \link #exploreIter \endlink - */ - template - class WrappedSequence - : public IT - { - public: - WrappedSequence() - : IT() - { } - - WrappedSequence(IT const& srcIter) - : IT(srcIter) - { } - - - /* === Iteration control API for IterStateWrapper == */ - - friend bool - checkPoint (WrappedSequence const& sequence) - { - return bool(sequence); - } - - friend typename IT::reference - yield (WrappedSequence const& sequence) - { - return *sequence; - } - - friend void - iterNext (WrappedSequence & sequence) - { - ++sequence; - } - }; - - - template - struct DepthFirst - : IterExplorer, DepthFirstEvaluationCombinator> - { - DepthFirst() { }; - DepthFirst(SRC const& srcSeq) - : IterExplorer, DepthFirstEvaluationCombinator> (srcSeq) - { } - }; - - template - struct BreadthFirst - : IterExplorer, BreadthFirstEvaluationCombinator> - { - BreadthFirst() { }; - BreadthFirst(SRC const& srcSeq) - : IterExplorer, BreadthFirstEvaluationCombinator> (srcSeq) - { } - }; - }//(End) namespace iter_explorer : predefined policies and configurations @@ -891,7 +156,8 @@ namespace lib { /* ==== convenient builder free functions ==== */ - + +/* template inline IterExplorer> exploreIter (IT const& srcSeq) @@ -915,69 +181,7 @@ namespace lib { return iter_explorer::BreadthFirst (srcSeq); } - - - template - inline iter_explorer::ChainedIters - iterChain(IT const& seq) - { - typename iter_explorer::ChainedIters::IteratorIterator sequenceOfIterators; - - sequenceOfIterators.push (seq); - return iter_explorer::ChainedIters(sequenceOfIterators); - } - - template - inline iter_explorer::ChainedIters - iterChain(IT const& seq1, IT const& seq2) - { - typename iter_explorer::ChainedIters::IteratorIterator sequenceOfIterators; - - sequenceOfIterators.push (seq2); - sequenceOfIterators.push (seq1); - return iter_explorer::ChainedIters(sequenceOfIterators); - } - - template - inline iter_explorer::ChainedIters - iterChain(IT const& seq1, IT const& seq2, IT const& seq3) - { - typename iter_explorer::ChainedIters::IteratorIterator sequenceOfIterators; - - sequenceOfIterators.push (seq3); - sequenceOfIterators.push (seq2); - sequenceOfIterators.push (seq1); - return iter_explorer::ChainedIters(sequenceOfIterators); - } - - template - inline iter_explorer::ChainedIters - iterChain(IT const& seq1, IT const& seq2, IT const& seq3, IT const& seq4) - { - typename iter_explorer::ChainedIters::IteratorIterator sequenceOfIterators; - - sequenceOfIterators.push (seq4); - sequenceOfIterators.push (seq3); - sequenceOfIterators.push (seq2); - sequenceOfIterators.push (seq1); - return iter_explorer::ChainedIters(sequenceOfIterators); - } - - template - inline iter_explorer::ChainedIters - iterChain(IT const& seq1, IT const& seq2, IT const& seq3, IT const& seq4, IT const& seq5) - { - typename iter_explorer::ChainedIters::IteratorIterator sequenceOfIterators; - - sequenceOfIterators.push (seq5); - sequenceOfIterators.push (seq4); - sequenceOfIterators.push (seq3); - sequenceOfIterators.push (seq2); - sequenceOfIterators.push (seq1); - return iter_explorer::ChainedIters(sequenceOfIterators); - } - - +*/ } // namespace lib diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp index 4acc199ae..465b24f17 100644 --- a/tests/library/iter-tree-explorer-test.cpp +++ b/tests/library/iter-tree-explorer-test.cpp @@ -75,7 +75,6 @@ namespace test{ using util::isnil; using util::isSameObject; using lib::iter_stl::eachElm; - using lib::iter_explorer::ChainedIters; using lumiera::error::LUMIERA_ERROR_ITER_EXHAUST; using std::string; @@ -134,10 +133,6 @@ namespace test{ NumberSequence(uint start, uint end) : IterStateWrapper (State(start,end)) { } - - /** allow using NumberSequence in LinkedElements - * (intrusive single linked list) */ - NumberSequence* next =nullptr; }; inline NumberSequence @@ -155,14 +150,6 @@ namespace test{ NumberSequence NIL_Sequence; - /** - * an arbitrary series of numbers - * @note deliberately this is another type - * and not equivalent to a NumberSequence, - * while both do share the same value type - */ - typedef IterQueue NumberSeries; - /** Diagnostic helper: "squeeze out" the given iterator * and join all the elements yielded into a string @@ -241,32 +228,23 @@ namespace test{ virtual void run (Arg) { - verifyStateAdapter(); + verify_wrappedIterator(); - verifyMonadOperator (); - verifyChainedIterators(); - verifyRawChainedIterators(); + verify_mapOperation(); + verify_expandOperation(); + verify_expandMapCombination(); - verifyDepthFirstExploration(); - verifyBreadthFirstExploration(); - verifyRecursiveSelfIntegration(); + verify_depthFirstExploration(); + demonstrate_LayeredEvaluation(); } - /** @test demonstrate the underlying solution approach of IterExplorer. - * All of the following IterExplorer flavours are built on top of a - * special iterator adapter, centred at the notion of an iterable state - * element type. The actual iterator just embodies one element of this - * state representation, and typically this element alone holds all the - * relevant state and information. Essentially this means the iterator is - * _self contained_. Contrast this to the more conventional approach of - * iterator implementation, where the iterator entity actually maintains - * a hidden back-link to some kind of container, which in turn is the one - * in charge of the elements yielded by the iterator. + /** @test without using any extra functionality, + * TreeExplorer just wraps an iterable state. */ void - verifyStateAdapter () + verify_wrappedIterator() { NumberSequence ii = seq(9); CHECK (!isnil (ii)); @@ -292,266 +270,49 @@ namespace test{ - /** @test verify a helper to chain a series of iterators into a "flat" result sequence. - * This convenience helper is built using IterExplorer building blocks. The resulting - * iterator _combines_ and _flattens_ a sequence of source iterators, resulting in a - * simple sequence accessible as iterator again. Here we verify the convenience - * implementation; this uses a STL container (actually std:deque) behind the scenes - * to keep track of all added source iterators. + /** @test pipe each result through a transformation function */ void - verifyChainedIterators () + verify_mapOperation() { - typedef ChainedIters Chain; - - Chain ci = iterChain (seq(5),seq(7),seq(9)); - - CHECK (!isnil (ci)); - pullOut (ci); - CHECK ( isnil (ci)); - VERIFY_ERROR (ITER_EXHAUST, *ci ); - VERIFY_ERROR (ITER_EXHAUST, ++ci ); - - CHECK (isnil(Chain())); - CHECK (!iterChain (NIL_Sequence)); - - // Iterator chaining "flattens" one level of packaging - NumberSequence s9 = seq(9); - ci = iterChain (s9); - - for ( ; s9 && ci; ++s9, ++ci ) - CHECK (*s9 == *ci); - - CHECK (isnil(s9)); - CHECK (isnil(ci)); - - // Note: Iterator chain is created based on (shallow) copy - // of the source sequences. In case these have an independent - // per-instance state (like e.g. NumberSequence used for this test), - // then the created chain is independent from the source iterators. - s9 = seq(9); - ci = iterChain (s9); - CHECK (0 == *s9); - CHECK (0 == *ci); - - pullOut (ci); - CHECK (isnil(ci)); - CHECK (0 == *s9); - pullOut (s9); - CHECK (isnil(s9)); + UNIMPLEMENTED("map function onto the results"); } - /** @test variation of the iterator chaining facility. - * This is the "raw" version without any convenience shortcuts. - * The source iterators are provided as iterator yielding other iterators. + /** @test use a preconfigured "expand" functor to recurse into children */ void - verifyRawChainedIterators () + verify_expandOperation() { - typedef std::vector IterContainer; - typedef RangeIter IterIter; - - typedef ChainedIters Chain; - - NumberSequence s5 (1,5); - NumberSequence s7 (5,8); - NumberSequence s9 (8,10); - - CHECK (1 == *s5); - CHECK (5 == *s7); - CHECK (8 == *s9); - - IterContainer srcIters; - srcIters.push_back (s5); - srcIters.push_back (s7); - srcIters.push_back (s9); - - IterIter iti = eachElm(srcIters); - CHECK (!isnil (iti)); - - // note: iterator has been copied - CHECK ( isSameObject (srcIters[0], *iti)); - CHECK (!isSameObject (s5, *iti)); - - Chain chain(iti); - CHECK (!isnil (iti)); - CHECK (1 == *chain); - - ++chain; - CHECK (2 == *chain); - - CHECK (1 == *s5); // unaffected of course... - CHECK (5 == *s7); - CHECK (8 == *s9); - - ++++chain; - CHECK (4 == *chain); - ++chain; - CHECK (5 == *chain); // switch over to contents of 2nd iterator - ++++++++chain; - CHECK (9 == *chain); - - ++chain; - CHECK (isnil(chain)); - VERIFY_ERROR (ITER_EXHAUST, *chain ); - VERIFY_ERROR (ITER_EXHAUST, ++chain ); + UNIMPLEMENTED("expand children"); } - - /** @test a depth-first visiting and exploration scheme of a tree like system, - * built on top of the IterExplorer monad. - * - * ## Test data structure - * We build a functional datastructure here, on the fly, while exploring it. - * The `exploreChildren(m)` function generates this tree like datastructure: - * For a given number, it tries to divide by 5, 3 and 2 respectively, possibly - * generating multiple decimation sequences. - * - * If we start such a tree structure e.g. with a root node 30, this scheme yields: - * \code - * ( 30 ) - * ( 6 10 15 ) - * ( 2 3 2 5 3 5 ) - * ( 1 1 1 1 1 1 ) - * \endcode - * This tree has no meaning in itself, beyond being an easy testbed for tree exploration schemes. - * - * ## How the exploration works - * We use a pre defined Template \ref DepthFirstExplorer, which is built on top of IterExplorer. - * It contains the depth-first exploration strategy in a hardwired fashion. Actually this effect is - * achieved by defining a specific way how to _combine the results of an 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 - * an iterator, but a specific iterator which at the same time is a Monad: It supports the `>>=` operation - * (also known as _bind_ operator or _flatMap_ operator). This operator takes as second argument a function, - * which in our case is the function to generate or explore the data structure. - * - * The result of applying this monadic `>>=` operation is a _transformed_ version of the source iterator, - * i.e. it is again an iterator, which yields the results of the exploration function, combined together - * in the order as defined by the built-in exploration strategy (here: depth first) - * - * @note technical detail: the result type of the exploration function (here `exploreChildren()`) determines - * the iterator type used within IterExplorer and to drive the evaluation. The source sequence used to - * seed the evaluation process can actually be any iterator yielding assignment compatible values: The - * second example uses a NumberSequence with unsigned int values 0..6, while the actual expansion and - * evaluation is based on NumberSeries using signed int values. + /** @test combie the recursion into children with a tail mapping operation */ void - verifyDepthFirstExploration () + verify_expandMapCombination() { - NumberSeries root = elements(30); - string explorationResult = materialise (depthFirst(root) >>= exploreChildren); - CHECK (explorationResult == "30-6-2-1-3-1-10-2-1-5-1-15-3-1-5-1"); - - NumberSequence to7 = seq(7); - explorationResult = materialise (depthFirst(to7) >>= exploreChildren); - CHECK (explorationResult == "0-1-2-1-3-1-4-2-1-5-1-6-2-1-3-1"); + UNIMPLEMENTED("combine child expansion and result mapping"); } - - - /** @test a breadth-first visiting and exploration scheme of a tree like system, - * built on top of the IterExplorer monad. - * Here, an internal queue is used to explore the hierarchy in layers. - * The (functional) datastructure is the same, just we're visiting it - * in a different way here, namely in rows or layers. + /** @test use a preconfigured exploration scheme to expand depth-first until exhaustion */ void - verifyBreadthFirstExploration () + verify_depthFirstExploration() { - NumberSeries root = elements(30); - string explorationResult = materialise (breadthFirst(root) >>= exploreChildren); - CHECK (explorationResult == "30-6-10-15-2-3-2-5-3-5-1-1-1-1-1-1"); + UNIMPLEMENTED("preconfigured repeated depth-first expansion"); } - - /** @test verify a variation of recursive exploration, this time to rely - * directly on the result set iterator type to provide the re-integration - * of intermediary results. Since our `exploreChildren()` function returns - * a NumberSeries, which basically is a IterQueue, the re-integration of expanded - * elements will happen at the end, resulting in breadth-first visitation order -- - * but contrary to the dedicated `breadthFirst(..)` explorer, this expansion is done - * separately for each element in the initial seed sequence. Note for example how the - * expansion series for number 30, which is also generated in verifyBreadthFirstExploration(), - * appears here at the end of the explorationResult sequence - * @remarks this "combinator strategy" is really intended for use with custom sequences, - * where the "Explorer" function works together with a specific implementation - * and exploits knowledge about specifically tailored additional properties of - * the input sequence elements, in order to yield the desired overall effect. - * Actually this is what we use in the proc::engine::Dispatcher to generate - * a series of frame render jobs, including all prerequisite jobs + /** @test Demonstration how to build complex algorithms by layered tree expanding iteration + * @remarks this is the actual use case which inspired the design of TreeExplorer */ void - verifyRecursiveSelfIntegration () + demonstrate_LayeredEvaluation() { - typedef IterExplorer - ,iter_explorer::RecursiveSelfIntegration> SelfIntegratingExploration; - - NumberSeries root = elements(10,20,30); - SelfIntegratingExploration exploration(root); - string explorationResult = materialise (exploration >>= exploreChildren); - CHECK (explorationResult == "10-2-5-1-1-20-4-10-2-2-5-1-1-1-30-6-10-15-2-3-2-5-3-5-1-1-1-1-1-1"); - } - - - - /** @test cover the basic monad bind operator, - * which is used to build all the specialised Iterator flavours. - * The default implementation ("Combinator strategy") just joins and flattens the result sequences - * created by the functor bound into the monad. For this test, we use a functor `explode(top)`, - * which returns the sequence 0...top. - */ - void - verifyMonadOperator () - { - auto explode = [](uint top) { return seq(0,top); }; - - // IterExplorer as such is an iterator wrapping the source sequence - string result = materialise (exploreIter(seq(5))); - CHECK (result == "0-1-2-3-4"); - - // now, if the source sequence yields exactly one element 5... - result = materialise (exploreIter(seq(5,6))); - CHECK (result == "5"); - - // then binding the explode()-Function yields just the result of invoking explode(5) - result = materialise (exploreIter(seq(5,6)) >>= explode); - CHECK (result == "0-1-2-3-4"); - - // binding anything into an empty sequence still results in an empty sequence - result = materialise (exploreIter(seq(0)) >>= explode); - CHECK (result == ""); - - // also, in case the bound function yields an empty sequence, the result remains empty - result = materialise (exploreIter(seq(1)) >>= explode); - CHECK (result == ""); - - // combining an empty sequence and the one element sequence (seq(0,1)) results in just one element - result = materialise (exploreIter(seq(2)) >>= explode); - CHECK (result == "0"); - - // multiple result sequences will be joined (flattened) into one sequence - result = materialise (exploreIter(seq(5)) >>= explode); - CHECK (result == "0-0-1-0-1-2-0-1-2-3"); - - // since the result is a monad, we can again bind yet another function - result = materialise((exploreIter(seq(5)) >>= explode) >>= explode); - CHECK (result == "0-0-0-1-0-0-1-0-1-2"); - - // Explanation: - // 0 -> empty sequence, gets dropped - // 1 -> 1-element sequence {0} - // 2 -> {0,1} - // 3 -> {0,1,2} - - // Note: when cascading multiple >>= the parentheses are necessary, since in C++ unfortunately - // the ">>=" associates to the right, while the proper monad bind operator should associate to the left + UNIMPLEMENTED("build algorithm by layering iterator evaluation"); } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index ce265d59b..fd0416b6a 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -4721,8 +4721,7 @@ echte Expand-Funktion notwendig

- - + @@ -4757,8 +4756,7 @@ also mehr als bloß parametrisierte Typen (Templates)!

- - +
@@ -4782,8 +4780,7 @@ alles mit einer Form des IterExplorer machbar ist

- - + @@ -4802,8 +4799,7 @@ ...was nicht grade zur Verständlichkeit des Ganzen beiträgt

- -
+
@@ -4815,8 +4811,7 @@ das Fortschreiten der Berechnung dargestellt werden kann

- -
+
@@ -4839,8 +4834,7 @@ Problem: Layer sind verkoppelt

- - +
@@ -4856,8 +4850,7 @@ dann aber als State Monad

- - +
@@ -4875,8 +4868,7 @@                             in (f x) s

- - + @@ -4897,8 +4889,7 @@ auf den Zwischenzustand x aus (1) an

- - +
@@ -4926,8 +4917,7 @@ wirklich hilfreich?

- - + @@ -4972,7 +4962,7 @@
- + @@ -4985,8 +4975,7 @@ brauche...

- -
+ @@ -5008,8 +4997,7 @@ das impliziert grundsätzlich einen Stack

- - +
@@ -5021,7 +5009,8 @@
- + + @@ -5030,7 +5019,8 @@ - + + @@ -5047,8 +5037,7 @@  für diesen Expand-Mechanismus auf

- - +
@@ -5074,8 +5063,7 @@ indem man einen speziellen Inline-Stack mit Heap-Overflow nutzt

- - +
@@ -5086,6 +5074,10 @@ + + + + @@ -5108,6 +5100,24 @@ + + + + + + + + + + + + + + + + + +