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

- - +