From 782b4f949f96b82c8a95dc44d86dbcbf8ec6e51e Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 17 Nov 2017 21:43:50 +0100 Subject: [PATCH] TreeExplorer: extended analysis regarding tree expanding and backtracking computation (#1117) This can be seen as a side track, but the hope is by relying on some kind of monadic evaluation pattern, we'll be able to to reconcile the IterExplorer draft from 2012 with the requirement to keep the implementation of "tree position" entirely opaque. The latter is mandatory in the use case here, since we must not intermingle the algorithm to resolve UI-coordinates in any way with the code actually navigating and accessing GTK widgets. Thus, we're forced to build some kind of abstraction barrier, and this turns out to be surprisingly difficult. --- src/gui/interact/ui-coord-resolver.cpp | 28 +- src/gui/interact/ui-coord-resolver.hpp | 39 +- src/lib/iter-explorer.hpp | 2 + src/lib/iter-tree-explorer.hpp | 984 ++++++++++++++++++++ tests/library/iter-explorer-test.cpp | 2 + tests/library/iter-tree-explorer-test.cpp | 564 ++++++++++++ wiki/thinkPad.ichthyo.mm | 1022 +++++++++++++++------ 7 files changed, 2348 insertions(+), 293 deletions(-) create mode 100644 src/lib/iter-tree-explorer.hpp create mode 100644 tests/library/iter-tree-explorer-test.cpp diff --git a/src/gui/interact/ui-coord-resolver.cpp b/src/gui/interact/ui-coord-resolver.cpp index 281dc2d71..0883c65c3 100644 --- a/src/gui/interact/ui-coord-resolver.cpp +++ b/src/gui/interact/ui-coord-resolver.cpp @@ -41,11 +41,35 @@ using lib::Symbol; namespace gui { namespace interact { - /** */ + /** @internal working data for path resolution */ + struct UICoordResolver::ResolutionState + { + using ChildIter = LocationQuery::ChildIter; + + lib::IterStack backlog; + lib::IterQueue solutions; + }; + + namespace { // + + + }//(End) implementation details + + + + /** + * Since UICoord path specifications may contain gaps and wildcards, we may attempt + * to fill in these missing parts by matching against the topological structure of an actual UI. + * In the general case, finding a solution requires a depth-first exponential brute-force search + * over the whole structure tree, since we have to try every possible branch until we can disprove + * the possibility of a match. Implemented as depth-first search with backtracking, this scanning + * pass produces a list of possible matches, from which we pick the first one with maximum + * coverage, to produce a single solution. + */ bool UICoordResolver::pathResolution() { - UNIMPLEMENTED ("backtracking reolution of path wildcards"); + } diff --git a/src/gui/interact/ui-coord-resolver.hpp b/src/gui/interact/ui-coord-resolver.hpp index d64e5fd3a..c034bfd0e 100644 --- a/src/gui/interact/ui-coord-resolver.hpp +++ b/src/gui/interact/ui-coord-resolver.hpp @@ -131,28 +131,8 @@ namespace interact { - namespace path { ///< @internal implementation details of UICoord resolution against actual UI - - struct Resolution - { - const char* anchor = nullptr; - size_t depth = 0; - std::unique_ptr covfefe; - bool isResolved = false; - }; - - struct ResolutionState - { - using ChildIter = LocationQuery::ChildIter; - - lib::IterStack backlog; - lib::IterQueue solutions; - }; - - }//(End) implementation details - - - + + /** * Query and mutate UICoord specifications in relation to actual UI topology. * @@ -161,8 +141,19 @@ namespace interact { class UICoordResolver : public UICoord::Builder { + + struct Resolution + { + const char* anchor = nullptr; + size_t depth = 0; + std::unique_ptr covfefe; + bool isResolved = false; + }; + LocationQuery& query_; - path::Resolution res_; + Resolution res_; + + struct ResolutionState; public: UICoordResolver (UICoord const& uic, LocationQuery& queryAPI) @@ -301,7 +292,7 @@ namespace interact { res_.isResolved = true; } - /** @internal algorithm to resolve this UICoord path against the actual UI topology */ + /** @internal algorithm to resolve this UICoord path against the actual UI topology. */ bool pathResolution(); }; diff --git a/src/lib/iter-explorer.hpp b/src/lib/iter-explorer.hpp index 800cec49d..004507de3 100644 --- a/src/lib/iter-explorer.hpp +++ b/src/lib/iter-explorer.hpp @@ -73,6 +73,8 @@ ** All the other preconfigured variants defined here where created as proof-of-concept, to document ** and verify this implementation technique as such. ** + ** @todo as of 2017, this framework is deemed incomplete and requires more design work. ////////////////////TICKET #1116 + ** ** @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, diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp new file mode 100644 index 000000000..603000d44 --- /dev/null +++ b/src/lib/iter-tree-explorer.hpp @@ -0,0 +1,984 @@ +/* + ITER-TREE-EXPLORER.hpp - building blocks for iterator evaluation strategies + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file iter-tree-explorer.hpp + ** Building tree expanding and backtracking evaluations within hierarchical scopes. + ** 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. + ** The implementation is based on the IterStateWrapper, which is one of the basic helper templates + ** provided by iter-adapter.hpp. + ** + ** @remark as of 2017, this template, as well as the initial IterExplorer (draft from 2012) can be + ** seen as steps towards designing a framework of building blocks for tree expanding and + ** backtracking algorithms. Due to the nature of Lumiera's design, we repeatedly encounter + ** this kind of computation pattern, when it comes to matching flexible configuration against + ** a likewise hierarchical and rules based model. To keep the code base maintainable, + ** we deem it crucial to reduce the inherent complexity in such algorithms by clearly + ** separate the _mechanics of evaluation_ from the actual logic of the target domain. + ** + ** # 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 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 + ** 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 + ** - 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) + ** + ** \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 + ** + ** 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 iter-adapter.hpp + ** @see itertools.hpp + ** @see IterSource (completely opaque iterator) + ** + */ + + +#ifndef LIB_ITER_TREE_EXPLORER_H +#define LIB_ITER_TREE_EXPLORER_H + + +#include "lib/error.hpp" +#include "lib/meta/function.hpp" +#include "lib/iter-adapter.hpp" +#include "lib/iter-stack.hpp" +#include "lib/meta/trait.hpp" ////////////////TODO +#include "lib/null-value.hpp" ////////////////TODO +#include "lib/util.hpp" + +#include ////////////////TODO +#include ////////////////TODO + + +namespace lib { + + namespace iter_explorer { + + template + class DefaultCombinator; + } + + + + /** + * Adapter to build a demand-driven tree expanding and exploring computation + * based on a custom opaque _state core_. TreeExploer adheres to the _Monad_ + * pattern known from functional programming, insofar the _expansion step_ is + * tied into the basic template by means of a function provided at usage site. + * + * @todo WIP -- preliminary draft as of 11/2017 + */ + template class _COM_ = iter_explorer::DefaultCombinator + > + class IterExplorer + : public IterStateWrapper + { + + + public: + typedef typename SRC::value_type value_type; + 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() { } + + + /** 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. + */ + explicit + IterExplorer (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; + } + }; + + + + + + + + + + + namespace iter_explorer { ///< predefined "exploration strategies", policies and configurations + + 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 + + + + + /* ==== convenient builder free functions ==== */ + + template + inline IterExplorer> + exploreIter (IT const& srcSeq) + { + return IterExplorer> (srcSeq); + } + + + template + inline iter_explorer::DepthFirst + depthFirst (IT const& srcSeq) + { + return iter_explorer::DepthFirst (srcSeq); + } + + + template + inline iter_explorer::BreadthFirst + breadthFirst (IT const& srcSeq) + { + 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 +#endif /* LIB_ITER_TREE_EXPLORER_H */ diff --git a/tests/library/iter-explorer-test.cpp b/tests/library/iter-explorer-test.cpp index e4c0c1427..b8e5d093c 100644 --- a/tests/library/iter-explorer-test.cpp +++ b/tests/library/iter-explorer-test.cpp @@ -47,6 +47,8 @@ ** Often, the very reason we're using such a setup is the ability to represent ** infinite structures. Like e.g. the evaluation graph of video passed through ** a complex processing pipeline. + ** + ** @todo as of 2017, this framework is deemed incomplete and requires more design work. ////////////////////TICKET #1116 */ diff --git a/tests/library/iter-tree-explorer-test.cpp b/tests/library/iter-tree-explorer-test.cpp new file mode 100644 index 000000000..4acc199ae --- /dev/null +++ b/tests/library/iter-tree-explorer-test.cpp @@ -0,0 +1,564 @@ +/* + IterTreeExplorer(Test) - verify tree expanding and backtracking iterator + + Copyright (C) Lumiera.org + 2017, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + +/** @file iter-tree-explorer-test.cpp + ** The \ref IterTreeExplorer_test covers and demonstrates a generic mechanism + ** to expand and evaluate tree like structures. In its current shape (as of 2017), + ** it can be seen as an preliminary step towards retrofitting IterExplorer into + ** a framework of building blocks for tree expanding and backtracking evaluations. + ** Due to the nature of Lumiera's design, we repeatedly encounter this kind of + ** algorithms, when it comes to matching configuration and parametrisation against + ** a likewise hierarchical and rules based model. To keep the code base maintainable, + ** we deem it crucial to reduce the inherent complexity in such algorithms by clearly + ** separate the _mechanics of evaluation_ from the actual logic of the target domain. + ** + ** Similar to IterExplorer_test, the his test relies on a demonstration setup featuring + ** a custom encapsulated state type: we rely on a counter with start and end value, + ** embedded into an iterator. Basically, this running counter, when iterated, generates + ** a sequence of numbers start ... end. + ** So -- conceptually -- this counting iterator can be thought to represent this + ** sequence of numbers. Note that this is a kind of abstract or conceptual + ** representation, not a factual representation of the sequence in memory. + ** The whole point is _not to represent_ this sequence in runtime state at once, + ** rather to pull and expand it on demand. + ** + ** All these tests work by first defining these _functional structures_, which just + ** yields an iterator entity. We get the whole structure it conceptually defines + ** only if we "pull" this iterator until exhaustion -- which is precisely what + ** the test does to verify proper operation. Real world code of course would + ** just not proceed in this way, like pulling everything from such an iterator. + ** Often, the very reason we're using such a setup is the ability to represent + ** infinite structures. Like e.g. the evaluation graph of video passed through + ** a complex processing pipeline. + */ + + + +#include "lib/test/run.hpp" +#include "lib/test/test-helper.hpp" +#include "lib/iter-adapter-stl.hpp" +#include "lib/format-cout.hpp" +#include "lib/format-util.hpp" +#include "lib/util.hpp" + +#include "lib/iter-tree-explorer.hpp" +#include "lib/meta/trait.hpp" + +#include +#include +#include + + +namespace lib { +namespace test{ + + using ::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; + + + namespace { // test substrate: simple number sequence iterator + + /** + * This iteration _"state core" type_ describes + * a sequence of numbers yet to be delivered. + */ + class State + { + uint p,e; + + public: + State(uint start, uint end) + : p(start) + , e(end) + { } + + friend bool + checkPoint (State const& st) + { + return st.p < st.e; + } + + friend uint& + yield (State const& st) + { + return util::unConst(checkPoint(st)? st.p : st.e); + } + + friend void + iterNext (State & st) + { + if (!checkPoint(st)) return; + ++st.p; + } + }; + + + + /** + * A straight ascending number sequence as basic test iterator. + * The tests will dress up this source sequence in various ways. + */ + class NumberSequence + : public IterStateWrapper + { + + public: + explicit + NumberSequence(uint end = 0) + : IterStateWrapper (State(0,end)) + { } + NumberSequence(uint start, uint end) + : IterStateWrapper (State(start,end)) + { } + + /** allow using NumberSequence in LinkedElements + * (intrusive single linked list) */ + NumberSequence* next =nullptr; + }; + + inline NumberSequence + seq (uint end) + { + return NumberSequence(end); + } + + inline NumberSequence + seq (uint start, uint end) + { + return NumberSequence(start, end); + } + + 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 + */ + template + inline string + materialise (II&& ii) + { + return util::join (std::forward (ii), "-"); + } + + template + inline void + pullOut (II & ii) + { + while (ii) + { + cout << *ii; + if (++ii) cout << "-"; + } + cout << endl; + } + + } // (END) test helpers + + + + + + + + /*******************************************************************//** + * @test use a simple source iterator yielding numbers + * to build various functional evaluation structures, + * based on the \ref IterExplorer template. + * - the [state adapter](\ref verifyStateAdapter() ) + * iterator construction pattern + * - helper to [chain iterators](\ref verifyChainedIterators() ) + * - building [tree exploring structures](\ref verifyDepthFirstExploration()) + * - the [monadic nature](\ref verifyMonadOperator()) of IterExplorer + * - a [recursively self-integrating](\ref verifyRecrusiveSelfIntegration()) + * evaluation pattern + * + * ## Explanation + * + * Both this test and the IterExplorer template might be bewildering + * and cryptic, unless you know the *Monad design pattern*. Monads are + * heavily used in functional programming, actually they originate + * from Category Theory. Basically, Monad is a pattern where we + * combine several computation steps in a specific way; but instead + * of intermingling the individual computation steps and their + * combination, the goal is to isolate and separate the _mechanics + * of combination_, so we can focus on the actual _computation steps:_ + * The mechanics of combination are embedded into the Monad type, + * which acts as a kind of container, holding some entities + * to be processed. The actual processing steps are then + * fed to the monad as "function object" parameters. + * + * Using the monad pattern is well suited when both the mechanics of + * combination and the individual computation steps tend to be complex. + * In such a situation, it is beneficial to develop and test both + * in isolation. The IterExplorer template applies this pattern + * to the task of processing a source sequence. Typically we use + * this in situations where we can't afford building elaborate + * data structures in (global) memory, but rather strive at + * doing everything on-the-fly. A typical example is the + * processing of a variably sized data set without + * using heap memory for intermediary results. + * + * @see TreeExplorer + * @see IterAdapter + */ + class IterTreeExplorer_test : public Test + { + + virtual void + run (Arg) + { + verifyStateAdapter(); + + verifyMonadOperator (); + verifyChainedIterators(); + verifyRawChainedIterators(); + + verifyDepthFirstExploration(); + verifyBreadthFirstExploration(); + verifyRecursiveSelfIntegration(); + } + + + + /** @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. + */ + void + verifyStateAdapter () + { + NumberSequence ii = seq(9); + CHECK (!isnil (ii)); + CHECK (0 == *ii); + ++ii; + CHECK (1 == *ii); + pullOut(ii); + CHECK ( isnil (ii)); + CHECK (!ii); + + VERIFY_ERROR (ITER_EXHAUST, *ii ); + VERIFY_ERROR (ITER_EXHAUST, ++ii ); + + ii = seq(5); + CHECK (materialise(ii) == "0-1-2-3-4"); + ii = seq(5,8); + CHECK (materialise(ii) == "5-6-7"); + + ii = NIL_Sequence; + CHECK ( isnil (ii)); + CHECK (!ii); + } + + + + /** @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. + */ + void + verifyChainedIterators () + { + 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)); + } + + + /** @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. + */ + void + verifyRawChainedIterators () + { + 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 ); + } + + + + /** @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. + */ + void + verifyDepthFirstExploration () + { + 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"); + } + + + + + /** @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. + */ + void + verifyBreadthFirstExploration () + { + 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"); + } + + + + /** @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 + */ + void + verifyRecursiveSelfIntegration () + { + 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 + } + }; + + + + LAUNCHER (IterTreeExplorer_test, "unit common"); + + +}} // namespace lib::test + diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 34bcb1503..ce265d59b 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -233,8 +233,7 @@ aber nur via einfacher "uplink"-Verbindung

- - + @@ -292,8 +291,7 @@ Term-Signal nicht ausgesendet würde.

- - +
@@ -377,8 +375,7 @@ Anmerkung: ein "frestehendes" BusTerm ist valide und zugelassen, es hat halt nur eine uplink-Connection.

- - +
@@ -393,8 +390,7 @@ es muß dazu auch jede Menge Methoden implementieren.

- -
+
@@ -567,8 +563,7 @@ dann kann der Shutdown-Prozeß den Start des GUI überholen.

- - +
@@ -808,8 +803,7 @@ und ihre "Methoden" sind Commands auf der Session!

- - +
@@ -1182,8 +1176,7 @@ Das wird eine ganze Me

- - + @@ -1447,8 +1440,7 @@ indem wir ein GTK-Signal erzeugen, das das Hauptfenster schließt

- - + @@ -1555,8 +1547,7 @@ ...indem der NotificatonService nun vom UI-Manager gemanaged wird :)

- - +
@@ -2762,8 +2753,7 @@ daß die alte, obsolete Timeline zurückgebaut ist

- - + @@ -2782,8 +2772,7 @@ bevor die Notification-Facade geöffnet werden konnte

- - +
@@ -2888,8 +2877,7 @@ ist, daß Gio::Application sofort auch gleich eine dBus-Verbindung hochfährt.

- - + @@ -3010,8 +2998,7 @@ diesen "aktuellen Kontext" irgendwo aufzufischen

- - + @@ -3193,8 +3180,7 @@ InvocationTrail ist tot

- - +
@@ -3376,8 +3362,7 @@ da es nur darum geht, via globalCtx auf den passenden Controller zuzugreifen

- - +
@@ -3620,8 +3605,7 @@ ...and this anchorage can be covered and backed by the currently existing UI configuration

- - +
@@ -3633,8 +3617,7 @@ ...by interpolation of some wildcards

- -
+
@@ -3646,8 +3629,7 @@ ...need to be extended to allow anchoring

- -
+
@@ -3685,8 +3667,7 @@ we may construct the covered part of a given spec, including automatic anchoring.

- - + @@ -3708,8 +3689,7 @@ designated by the given coordinate spec

- - + @@ -3738,8 +3718,7 @@ und dann kann man es auch extern belassen.

- - +
@@ -3773,8 +3752,7 @@ sind denkbar und müssen in der Strategy konfigurierbar sein?

- - + @@ -3901,8 +3879,7 @@ betrachte ich als ungesund

- - +
@@ -3934,8 +3911,7 @@ eine reverse resolution

- - +
@@ -3947,8 +3923,7 @@ zentrales Problem

- -
+
@@ -3968,8 +3943,7 @@ mit den zu diesem Zeitpunkt bekannten UI-Korrdinaten bestückt wird

- - +
@@ -3993,8 +3967,7 @@ stützen wir uns?

- - + @@ -4037,8 +4010,7 @@ was "Kind" ist?

- - + @@ -4079,8 +4051,7 @@ oder den Spot verschieben

- - +
@@ -4097,8 +4068,7 @@ Probleme

- - + @@ -4250,8 +4220,7 @@ sind wirklich notwendig

- - +
@@ -4301,8 +4270,7 @@ Grund: wir wollen vermeiden, abschließende Wildcards bloß irgendwie zu binden

- - +
@@ -4317,8 +4285,7 @@ dann aber Wildcards enthält, die nicht nach den verschärften Bedingungen gecovert werden können.

- -
+
@@ -4341,8 +4308,7 @@ - - +
@@ -4378,8 +4344,7 @@ denn eine Verletzung kann weithin unbemerkt bleiben

- - + @@ -4405,8 +4370,7 @@ daraus resultierenden Pfad zugreift

- - + @@ -4426,8 +4390,7 @@ Hierbei ist aktive Lebensdauer wie bei einem Iterator zu verstehen.

- - + @@ -4444,8 +4407,7 @@ wenn die Auswertung aufgrund einer gebrochenen Konvention entgleist

- - + @@ -4470,8 +4432,7 @@ Es gilt die erste maximal abdeckende Lösung

- - + @@ -4495,8 +4456,7 @@ Sofern der Pfad bereits explizit ist, genügt diese Info allein

- - +
@@ -4517,8 +4477,7 @@ - - + @@ -4547,6 +4506,627 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Ha! das ist eine Monade!!!!!1!11! +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ...aber im Moment der Lösung brauche ich den Pfad aufwärts. +

+ +
+
+ + + + + + +

+ Das heißt, wir müssen ihn bereits im Aufrufkontext bereit liegen haben +

+ +
+
+ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ Puffer ab Tiefe +

+

+ vom Pfad initialisieren +

+ +
+ +
+
+ + + + + + +

+ monadische Lösung +

+

+ möglich? +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ echte Expand-Funktion notwendig +

+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +

+ mathematische Monaden sind viel mehr... +

+

+ Im Besonderen sind es Typen höherer Ordnung, +

+

+ also mehr als bloß parametrisierte Typen (Templates)! +

+ + +
+
+ + + + + + + + + + + + + + + + + + +

+ alles mit einer Form des IterExplorer machbar ist +

+ + +
+ + + + + +

+ ...da IterExplorer einen Template-Template-Parameter nimmt, +

+

+ ist er eigentlich ein Meta-Template, und es gibt diverse +

+

+ Ausprägungen, die alle subtile Seiteneffekte ausnutzen.... +

+

+ ...was nicht grade zur Verständlichkeit des Ganzen beiträgt +

+ + +
+
+ + + + + + +

+ das Fortschreiten der Berechnung dargestellt werden kann +

+ + +
+
+ + + + + + + + + + + + + + + + + +

+ Problem: Layer sind verkoppelt +

+ + +
+ +
+
+ + + + + + + + +

+ dann aber als State Monad +

+ + +
+
+ + + + + + + + + +

+ m >>= f = \r -> let (x, s) = m r +

+

+                             in (f x) s +

+ + +
+ + + + + + + + + + + + + +

+ wende die resultierende State-Monade +

+

+ auf den Zwischenzustand x aus (1) an +

+ + +
+ +
+
+
+ + + +
+ + + + +
+
+ + + + + + +

+ sind Monaden +

+

+ wirklich hilfreich? +

+ + +
+ + + + + + + + + + + + + + + + + +

+ alternatives +

+

+ Ziel +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +

+ Fazit: +

+

+ brauche... +

+ + +
+ + + + + + + + + + + + + + + + + +

+ das impliziert grundsätzlich einen Stack +

+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ expand() ruft eine vorbereitete Parametrisierung +

+

+  für diesen Expand-Mechanismus auf +

+ + +
+
+ + + + + + + + +

+ ...selbst wenn man ihn für eine triviale Implementierung +

+

+ eigentlich überhaupt nicht braucht. +

+

+ +

+

+ Das kann zwar zu einem gewissen Grad abgemildert werden, +

+

+ indem man einen speziellen Inline-Stack mit Heap-Overflow nutzt +

+ + +
+
+
+ + + + +
+
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4563,8 +5143,7 @@ ...das heißt: keine Wildcards, keine pseudo-Specs (currentWindow)

- - +
@@ -4582,8 +5161,7 @@ Zweck ist vor allem, meta-Specs wie firstWindow, currentWindow aufzulösen

- - +
@@ -4610,8 +5188,7 @@ als Repräsentation des real-existierenden UI

- - +
@@ -4711,8 +5288,7 @@ Daher denkt der Compiler, er kann das Ursprungsobjekt jezt wergwerfen.

- - +
@@ -4729,8 +5305,7 @@ Perspektive

- - + @@ -4751,8 +5326,7 @@ bei der Navigation in einem realen GUI auftreten

- -
+
@@ -4810,8 +5384,7 @@ Schichten-Prinzip...

- - +
@@ -4885,8 +5458,7 @@ - - + @@ -4965,8 +5537,7 @@ bewirkt nur eine Entkoppelung vom Implementierungs-Kontext

- - +
@@ -5000,8 +5571,7 @@ sondern interpretieren jeweils nur eine einzige feste Konfiguration

- - +
@@ -5064,8 +5634,7 @@ um auszudrücken, daß gewissen Angaben ausgelassen wurden

- - +
@@ -5682,8 +6251,7 @@ in generischer UI-Struktur bewegen

- - +
@@ -5696,9 +6264,9 @@ + - @@ -5728,8 +6296,7 @@ ...denn das ist das vereinfachte Setup für "einfache" Applikationen

- - +
@@ -5902,8 +6469,7 @@ das Diff wird auf den Platzhalter angewendet

- - +
@@ -5918,8 +6484,7 @@ dann muß dieses automatisch deregistriert werden

- -
+
@@ -6200,8 +6765,7 @@ Details im  TiddlyWiki....

- -
+
@@ -6714,8 +7278,7 @@ ...abstraktes Interface

- - + @@ -6758,8 +7321,7 @@ oder eine Sequenz ohne root-Fork zulassen

- - + @@ -7201,8 +7763,7 @@ Verwende das als Leitgedanke, um das Layout zu entwickeln

- - + @@ -7363,8 +7924,7 @@ ...um mal was im UI anzeigen zu können

- - +
@@ -7393,8 +7953,7 @@ Die Icon-Größen ergeben sich aus den Boxes auf 'plate'

- - +
@@ -7442,8 +8001,7 @@ mehr als ein top-level Fenster offen

- - +
@@ -7604,8 +8162,7 @@ aber es kann davon mehrere geben

- - +
@@ -7896,8 +8453,7 @@ was haben alle UI-Elemente wirklich gemeinsam?

- - + @@ -7915,8 +8471,7 @@ oder handelt es sich nur um ein Implementierungsdetail der UI-Bus-Anbindung?

- -
+ @@ -14318,8 +14873,7 @@ - - + @@ -14350,8 +14904,7 @@ - - + @@ -14398,8 +14951,7 @@ Implementierung der real-world-Variante fehlt!

- - + @@ -14419,8 +14971,7 @@ wie Session- und State-Managment, Commands etc.

- - +
@@ -14713,8 +15264,7 @@ und ebenso die Gesten abstrahieren

- - +
@@ -14759,8 +15309,7 @@ - - + @@ -15044,8 +15593,7 @@ mit dem InteractionDirector verdrahtet sein muß!

- - + @@ -15062,8 +15610,7 @@ und auch nichts mit der Trennung zwischen Layern und Subsystemen

- - +
@@ -15078,8 +15625,7 @@ aka DependencyInjection + Lifecycle Management

- -
+ @@ -15104,8 +15650,7 @@ - - + @@ -15127,8 +15672,7 @@ es könnte auch ausreichen, einfach die passende InteractionStateManager-Impl zu verwenden

- - +
@@ -15141,8 +15685,7 @@ denn InteractionStateManager ist ein Interface!

- - +
@@ -15165,8 +15708,7 @@ Das könnte ein Advice sein

- - +
@@ -15187,8 +15729,7 @@ In diesem Fall wird das Command enabled

- -
+
@@ -15200,8 +15741,7 @@ eine Argumentliste mit mehreren Parametern wir Schritt für Schritt geschlossen

- -
+
@@ -15216,8 +15756,7 @@ wird das gemäß Scope "nächstgelegne" genommen

- -
+
@@ -15832,8 +16371,7 @@ - - + @@ -16002,8 +16540,7 @@ (Beispiel "in-point fehlt")

- - +
@@ -16021,8 +16558,7 @@ aber von einem externen State-Change getriggert wird

- -
+
@@ -16145,8 +16681,7 @@ CommandID.KontextID == Instanz

- - + @@ -16574,8 +17109,7 @@ Also genügt es, einen anonymen Klon dieser Instanz zu halten

- - +
@@ -16602,8 +17136,7 @@ Beispiel: Menü-Eintrag "create duplicate"

- - + @@ -16642,8 +17175,7 @@ oder andernfalls einen bestimmten Namen bekommt

- - + @@ -16686,8 +17218,7 @@ wieder komplett zurückgebaut habe

- - +
@@ -19432,8 +19963,7 @@ nach dem Umzug auf Github heißt es gason

- - + @@ -19619,8 +20149,7 @@ we need to wait for the current command or builder run to be completed

- -
+
@@ -19634,8 +20163,7 @@ ...noch nicht implementiert 1/17

- - + @@ -19664,8 +20192,7 @@ Guard beim Zugang über das Interface

- - +
@@ -19784,8 +20311,7 @@ OO rocks!

- -
+
@@ -19870,8 +20396,7 @@ - - +
@@ -20072,8 +20597,7 @@ - - +
@@ -20103,8 +20627,7 @@ Alles in ein Framework zwingen. Alternativlos, capisce?

- - +
@@ -20188,8 +20711,7 @@ Gtk-Main verwendet inzwischen den gleichen Mechanismus

- - +
@@ -20224,8 +20746,7 @@ Das ist eine subtile Falle.

- - +
@@ -20267,8 +20788,7 @@ alles das nicht aus dem GUI-Thread heraus geschieht

- - +
@@ -20318,8 +20838,7 @@ Siehe Beschreibung im Beispiel/Tutorial

- - +
@@ -20731,8 +21250,7 @@ Beachte: der Text-Cursor (Marker "insert") hat right gravity

- - +
@@ -20819,8 +21337,7 @@ Query<RES>::resolveBy

- - +
@@ -20857,8 +21374,7 @@ sonst kommt Doxygen durcheinander

- -
+
@@ -20887,8 +21403,7 @@ wird hier kein Link erzeugt

- - +
@@ -20913,8 +21428,7 @@ ...im Besonderen die guten Diagramme für Pulse, ALSA und Jack

- - +
@@ -20973,8 +21487,7 @@ "-Wl,-rpath-link=target/modules"

- - +
@@ -20987,8 +21500,7 @@ laufen wieder alle

- - + @@ -21005,8 +21517,7 @@ test.sh Zeile 138

- - +
@@ -21075,8 +21586,7 @@ ist klar, hab ich gebrochen

- - + @@ -21105,8 +21615,7 @@ Vorher hatte ich erste Kollisionen nach 25000 Nummern

- - +
@@ -21158,8 +21667,7 @@ Aug 10 04:51:39 flaucher kernel: traps: test-suite[8249] trap int3 ip:7ffff7deb241 sp:7fffffffe5c8 error:0

- -
+ @@ -21316,8 +21824,7 @@ und tatsächlich: das ist daneben, GCC hat Recht!

- - +
@@ -21330,8 +21837,7 @@ aktualisieren und neu bauen

- - +
@@ -21361,8 +21867,7 @@ wähle Kompatibiltät genau so, daß Ubuntu-Trusty noch unterstützt wird.

- - + @@ -21409,8 +21914,7 @@ Ich meine also: zu Beginn vom Build sollte das Buildsystem einmal eine Infozeile ausgeben

- - +
@@ -21422,8 +21926,7 @@ ...denn die stören jeweils beim erzeugen eines Hotfix/Patch im Paketbau per dpkg --commit

- -
+
@@ -21480,8 +21983,7 @@ Doku durchkämmen nach Müll

- - + @@ -21498,8 +22000,7 @@ WICHTIG: keine vorgreifende Infor publizieren!!!!!

- - +
@@ -21518,8 +22019,7 @@ insgesamt sorgfältig durchlesen

- - + @@ -21539,8 +22039,7 @@ knappe Kennzeichnung des Releases in den Kommentar

- - + @@ -21567,8 +22066,7 @@ denn wir wollen keine DEB-Info im Master haben!

- - + @@ -21594,8 +22092,7 @@ die unmittelbaren Release-Dokumente durchgehen

- - + @@ -21622,8 +22119,7 @@ Sollte konfliktfrei sein

- - +
@@ -21656,8 +22152,7 @@ ...das heißt bauen und hochladen

- - + @@ -21724,8 +22219,7 @@ unter Debian/Jessie wird das ignoriert

- - +
@@ -21753,8 +22247,7 @@ Die Wahrscheinlichkeit, daß irgend jemand Lumiera unter Ubuntu/Trusty installieren möchte, erscheint mir akademisch

- -
+
@@ -21790,8 +22283,7 @@ in lib/hash-standard.hpp

- - +
@@ -21826,8 +22318,7 @@ es gibt Probleme beim Linken mit den Boost-Libraries, die auf Ubuntu/wily mit gcc-5 gebaut sind.

- -
+
@@ -21845,8 +22336,7 @@ Wichtig: hier nur was wirklich gebaut ist und funktioniert!

- - +
@@ -21886,8 +22376,7 @@ bestehen, aber irgendwann müssen wir das schon glattziehen

- - +
@@ -21935,8 +22424,7 @@ seit gcc-4.8 ist kein static_assert mehr in der STDlib

- - +