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.
This commit is contained in:
Fischlurch 2017-11-17 21:43:50 +01:00
parent ca35891c41
commit 782b4f949f
7 changed files with 2348 additions and 293 deletions

View file

@ -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<ChildIter> backlog;
lib::IterQueue<Resolution> 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");
}

View file

@ -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<UICoord> covfefe;
bool isResolved = false;
};
struct ResolutionState
{
using ChildIter = LocationQuery::ChildIter;
lib::IterStack<ChildIter> backlog;
lib::IterQueue<Resolution> 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<UICoord> 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();
};

View file

@ -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,

View file

@ -0,0 +1,984 @@
/*
ITER-TREE-EXPLORER.hpp - building blocks for iterator evaluation strategies
Copyright (C) Lumiera.org
2017, Hermann Vosseler <Ichthyostega@web.de>
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 <b>Lumiera Forward Iterator</b> concept and using the basic IterAdaptor templates,
** these components allow to implement typical evaluation strategies, like e.g. depth-first or
** breadth-first exploration of a hierarchical structure. Since the access to this structure is
** abstracted through the underlying iterator, what we effectively get is a functional datastructure.
** 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 <boost/utility/enable_if.hpp> ////////////////TODO
#include <stack> ////////////////TODO
namespace lib {
namespace iter_explorer {
template<class SRC, class FUN>
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 SRC
,template<class,class> class _COM_ = iter_explorer::DefaultCombinator
>
class IterExplorer
: public IterStateWrapper<typename SRC::value_type, SRC>
{
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<class FUN>
struct FlatMapped
{
typedef IterExplorer<_COM_<IterExplorer,FUN>, _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<value_type, SRC> (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<class FUN>
typename FlatMapped<FUN>::Type
operator >>= (FUN explorer)
{
typedef _COM_<IterExplorer,FUN> Combinator; // instantiation of the combinator strategy
typedef typename FlatMapped<FUN>::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<class SIG>
struct ExploreByFunction
: function<SIG>
{
template<typename FUN>
ExploreByFunction(FUN explorationFunctionDefinition)
: function<SIG>(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<class SIG>
struct UnalteredPassThrough;
template<class IT>
struct UnalteredPassThrough<IT(IT)>
{
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 SRC, class FUN
,template<class> class _EXP_ = ExploreByFunction ///< Strategy: how to store and evaluate the function to apply on each element
>
class CombinedIteratorEvaluation
{
typedef typename _Fun<FUN>::Ret ResultIter;
typedef typename SRC::value_type SrcElement;
typedef _EXP_<ResultIter(SrcElement)> Explorer;
SRC srcSeq_;
ResultIter results_;
Explorer explorer_;
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 SRC, class FUN>
class DefaultCombinator
: public CombinedIteratorEvaluation<SRC,FUN>
{
typedef typename _Fun<FUN>::Ret ResultIter;
public:
DefaultCombinator() { }
DefaultCombinator(FUN explorerFunction, SRC const& sourceElements)
: CombinedIteratorEvaluation<SRC,FUN>(explorerFunction)
{
this->setSourceSequence (sourceElements);
}
};
/** Metafunction to detect an iterator yielding an iterator sequence */
template<class IT>
struct _is_iterator_of_iterators
{
typedef typename IT::value_type IteratorElementType;
enum{ value = meta::can_IterForEach<IteratorElementType>::value };
};
template<class ITI, class SEQ>
class ChainedIteratorImpl
: public CombinedIteratorEvaluation<ITI, SEQ(SEQ)
, UnalteredPassThrough
>
{ };
/**
* 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 ITI, class SEL = void>
class ChainedIters;
template<class ITI>
class ChainedIters<ITI, enable_if< _is_iterator_of_iterators<ITI>>
>
: public IterStateWrapper<typename ITI::value_type::value_type
,ChainedIteratorImpl<ITI, typename ITI::value_type>
>
{
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 SEQ>
class ChainedIters<SEQ, disable_if< _is_iterator_of_iterators<SEQ>>
>
: public IterStateWrapper<typename SEQ::value_type
,ChainedIteratorImpl<IterStack<SEQ>, SEQ>
>
{
public:
typedef IterStack<SEQ> 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 <i>must not</i>
* 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 SRC, class FUN
,template<class> class _BUF_
>
class RecursiveExhaustingEvaluation
{
typedef typename _Fun<FUN>::Ret ResultIter;
typedef typename _Fun<FUN>::Sig Sig;
typedef function<Sig> Explorer;
typedef _BUF_<ResultIter> 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> class _QUEUE_ ///< the actual container to use for storage of intermediary results
>
class EvaluationBufferStrategy
{
_QUEUE_<IT> 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<IT>::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<class IT>
struct DepthFirstEvaluationBuffer
: EvaluationBufferStrategy<IT, IterStack>
{ };
/**
* concrete strategy for recursive \em breadth-first evaluation.
* Using heap allocated storage in a STL Deque (used queue-like)
*/
template<class IT>
struct BreadthFirstEvaluationBuffer
: EvaluationBufferStrategy<IT, IterQueue>
{ };
/**
* preconfigured IterExplorer "state core" resulting in
* depth-first exhaustive evaluation
*/
template<class SRC, class FUN>
struct DepthFirstEvaluationCombinator
: RecursiveExhaustingEvaluation<SRC, FUN, DepthFirstEvaluationBuffer>
{
DepthFirstEvaluationCombinator() { }
DepthFirstEvaluationCombinator(FUN explorerFunction, SRC const& sourceElements)
: RecursiveExhaustingEvaluation<SRC, FUN, DepthFirstEvaluationBuffer> (explorerFunction,sourceElements)
{ }
};
/**
* preconfigured IterExplorer "state core" resulting in
* breadth-first exhaustive evaluation
*/
template<class SRC, class FUN>
struct BreadthFirstEvaluationCombinator
: RecursiveExhaustingEvaluation<SRC, FUN, BreadthFirstEvaluationBuffer>
{
BreadthFirstEvaluationCombinator() { }
BreadthFirstEvaluationCombinator(FUN explorerFunction, SRC const& sourceElements)
: RecursiveExhaustingEvaluation<SRC, FUN, BreadthFirstEvaluationBuffer> (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 <i>special result sequence</i> 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 SRC, class FUN>
class RecursiveSelfIntegration
{
typedef typename _Fun<FUN>::Ret ResultIter;
typedef typename _Fun<FUN>::Sig Sig;
typedef typename SRC::value_type Val;
typedef function<Sig> 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 IT>
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<class SRC>
struct DepthFirst
: IterExplorer<WrappedSequence<SRC>, DepthFirstEvaluationCombinator>
{
DepthFirst() { };
DepthFirst(SRC const& srcSeq)
: IterExplorer<WrappedSequence<SRC>, DepthFirstEvaluationCombinator> (srcSeq)
{ }
};
template<class SRC>
struct BreadthFirst
: IterExplorer<WrappedSequence<SRC>, BreadthFirstEvaluationCombinator>
{
BreadthFirst() { };
BreadthFirst(SRC const& srcSeq)
: IterExplorer<WrappedSequence<SRC>, BreadthFirstEvaluationCombinator> (srcSeq)
{ }
};
}//(End) namespace iter_explorer : predefined policies and configurations
/* ==== convenient builder free functions ==== */
template<class IT>
inline IterExplorer<iter_explorer::WrappedSequence<IT>>
exploreIter (IT const& srcSeq)
{
return IterExplorer<iter_explorer::WrappedSequence<IT>> (srcSeq);
}
template<class IT>
inline iter_explorer::DepthFirst<IT>
depthFirst (IT const& srcSeq)
{
return iter_explorer::DepthFirst<IT> (srcSeq);
}
template<class IT>
inline iter_explorer::BreadthFirst<IT>
breadthFirst (IT const& srcSeq)
{
return iter_explorer::BreadthFirst<IT> (srcSeq);
}
template<class IT>
inline iter_explorer::ChainedIters<IT>
iterChain(IT const& seq)
{
typename iter_explorer::ChainedIters<IT>::IteratorIterator sequenceOfIterators;
sequenceOfIterators.push (seq);
return iter_explorer::ChainedIters<IT>(sequenceOfIterators);
}
template<class IT>
inline iter_explorer::ChainedIters<IT>
iterChain(IT const& seq1, IT const& seq2)
{
typename iter_explorer::ChainedIters<IT>::IteratorIterator sequenceOfIterators;
sequenceOfIterators.push (seq2);
sequenceOfIterators.push (seq1);
return iter_explorer::ChainedIters<IT>(sequenceOfIterators);
}
template<class IT>
inline iter_explorer::ChainedIters<IT>
iterChain(IT const& seq1, IT const& seq2, IT const& seq3)
{
typename iter_explorer::ChainedIters<IT>::IteratorIterator sequenceOfIterators;
sequenceOfIterators.push (seq3);
sequenceOfIterators.push (seq2);
sequenceOfIterators.push (seq1);
return iter_explorer::ChainedIters<IT>(sequenceOfIterators);
}
template<class IT>
inline iter_explorer::ChainedIters<IT>
iterChain(IT const& seq1, IT const& seq2, IT const& seq3, IT const& seq4)
{
typename iter_explorer::ChainedIters<IT>::IteratorIterator sequenceOfIterators;
sequenceOfIterators.push (seq4);
sequenceOfIterators.push (seq3);
sequenceOfIterators.push (seq2);
sequenceOfIterators.push (seq1);
return iter_explorer::ChainedIters<IT>(sequenceOfIterators);
}
template<class IT>
inline iter_explorer::ChainedIters<IT>
iterChain(IT const& seq1, IT const& seq2, IT const& seq3, IT const& seq4, IT const& seq5)
{
typename iter_explorer::ChainedIters<IT>::IteratorIterator sequenceOfIterators;
sequenceOfIterators.push (seq5);
sequenceOfIterators.push (seq4);
sequenceOfIterators.push (seq3);
sequenceOfIterators.push (seq2);
sequenceOfIterators.push (seq1);
return iter_explorer::ChainedIters<IT>(sequenceOfIterators);
}
} // namespace lib
#endif /* LIB_ITER_TREE_EXPLORER_H */

View file

@ -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
*/

View file

@ -0,0 +1,564 @@
/*
IterTreeExplorer(Test) - verify tree expanding and backtracking iterator
Copyright (C) Lumiera.org
2017, Hermann Vosseler <Ichthyostega@web.de>
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 <utility>
#include <vector>
#include <string>
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<uint, State>
{
public:
explicit
NumberSequence(uint end = 0)
: IterStateWrapper<uint,State> (State(0,end))
{ }
NumberSequence(uint start, uint end)
: IterStateWrapper<uint,State> (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<int> NumberSeries;
/** Diagnostic helper: "squeeze out" the given iterator
* and join all the elements yielded into a string
*/
template<class II>
inline string
materialise (II&& ii)
{
return util::join (std::forward<II> (ii), "-");
}
template<class II>
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<NumberSequence> 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<NumberSequence> IterContainer;
typedef RangeIter<IterContainer::iterator> IterIter;
typedef ChainedIters<IterIter> 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::WrappedSequence<NumberSeries>
,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

File diff suppressed because it is too large Load diff