Lumiera
0.pre.03
»edit your freedom«
|
These tests build a evaluation pipeline by wrapping some kind of data source and then layering some evaluation stages on top. There are two motivations why one might want to build such a filter pipeline:
This usage style is inspired from the Monad design pattern. In our case here, the Iterator pipeline would be the monad, and can be augmented and reshaped by attaching further processing steps. How those processing steps are to be applied remains an internal detail, defined by the processing pipeline. »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 attached to the monad as "function object" parameters. It is up to the monad to decide if, and when those processing steps are applied to the embedded values and how to combine the results into a new monad.
Definition at line 262 of file iter-explorer-test.cpp.
Classes | |
struct | MagicTestRubbish |
demo of a custom processing layer interacting directly with the iteration mechanism. More... | |
Private Member Functions | |
void | demonstrate_LayeredEvaluation () |
virtual void | run (Arg) |
void | verify_aggregatingGroupItration () |
void | verify_asIterSource () |
void | verify_combinedExpandTransform () |
void | verify_customProcessingLayer () |
void | verify_dedup () |
void | verify_depthFirstExploration () |
void | verify_effuse () |
void | verify_elementGroupingOperation () |
void | verify_expand_rootCurrent () |
void | verify_expandOperation () |
void | verify_FilterChanges () |
void | verify_FilterIterator () |
void | verify_IterSource () |
void | verify_reduceVal () |
void | verify_scheduledExpansion () |
void | verify_transformOperation () |
template<class EXP > | |
void | verify_treeExpandingIterator (EXP ii) |
void | verify_untilStopTrigger () |
void | verify_wrappedIterator () |
void | verify_wrappedState () |
|
inlineprivate |
Definition at line 300 of file iter-explorer-test.cpp.
References lib::explore().
|
inlineprivate |
Definition at line 326 of file iter-explorer-test.cpp.
|
inlineprivate |
The expand()
builder function predefines a way how to expand the current head element of the iteration. However, expansion does not happen automatically, rather, it needs to be invoked by the client, similar to increment of the iterator. When expanding, the current head element is consumed and fed into the expand functor; the result of this functor invocation is injected instead into the result sequence, and consequently this result needs to be again an iterable with compatible value type. Conceptually, the evaluation forks into the children of the expanded element, before continuing with the successor of the expansion point. Obviously, expansion can be applied again on the result of the expansion, possibly leading to a tree of side evaluations.
The expansion functor may be defined in various ways and will be adapted appropriately
Definition at line 391 of file iter-explorer-test.cpp.
References lib::explore().
|
inlineprivate |
This feature was added to support a specific use-case in the IterChainSearch component. After expanding several levels deep into a tree, it allows to turn the current child sequence into a new root sequence and discard the whole rest of the tree, including the original root sequence. It is implemented by moving the current child sequence down into the root sequence. We demonstrate this behaviour with the simple standard setup from verify_expandOperation()
Definition at line 504 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
The transforming iterator is added as a decorator, wrapping the original iterator, TreeEplorer or state core. As you'd expect, the given functor is required to accept compatible argument types, and a generic lambda is instantiated to take a reference to the embedded iterator's value type. Several transformation steps can be chained, and the resulting entity is again a Lumiera Forward Iterator with suitable value type. The transformation function is invoked only once per step and the result produced by this invocation is placed into a holder buffer embedded within the iterator.
Definition at line 559 of file iter-explorer-test.cpp.
References lib::explore().
|
inlineprivate |
These groups are implemented as std::array and initialised with the values yielded consecutively from the underlying source pipeline. The main iterator then yields a reference to this data (which can be unpacked conveniently by a structured binding, or processed as a STL container. Moreover, there is a secondary interface, allowing to iterate over the values stored in this group; this is also exposed for the rest, which did not suffice to fill a full group.
Definition at line 679 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
Downstream, the resulting, accumulated value is exposed for each group, while consuming all source values belonging to this group.
Definition at line 740 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
Wile basically this is just the layering structure of IterExplorer put into action, you should note one specific twist: the iter_explorer::Expander::expandChildren() call is meant to be issued from ``downstream'', from the consumer side. Yet the consumer at that point might well see the items as processed by a transforming step layered on top. So what the consumer sees and thinks will be expanded need not actually be what will be processed by the expand functor. This may look like a theoretical or cosmetic issue – yet in fact it is this tiny detail which is crucial to make abstraction of the underlying data source actually work in conjunction with elaborate searching and matching algorithms. Even more so, when other operations like filtering are intermingled; in that case it might even happen that the downstream consumer does not even see the items resulting from child expansion, because they are evaluated and then filtered away by transformers and filters placed in between.
it.expandChildren()
to manipulate the underlying evaluation. However, since the overall evaluation is demand driven, there are inherent limitations to such a setup, which bends towards fragility when leaving the realm of pure functional evaluation. Definition at line 794 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
We demonstrate this extension mechanism here by defining a processing layer which skips each other element.
Definition at line 858 of file iter-explorer-test.cpp.
References lib::explore(), steam::mobject::session::test::anonymous_namespace{scope-query-test.cpp}::filter(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
As such, _"child expansion"_ happens right away, thereby consuming a node and replacing it with its child sequence. Sometimes, when building search and matching algorithms, we rather just want to plan a child expansion to happen on next increment. Such is especially relevant when searching for a locally or global maximal solution, which is rather simple to implement with an additional filtering layer – and this approach requires us to deliver all partial solutions for the filter layer to act on. Obviously this functionality leads to additional state and thus is provided as optional layer in the IterExplorer builder.
Definition at line 887 of file iter-explorer-test.cpp.
References lib::explore().
|
inlineprivate |
When decorating the pipeline with this adapter, iteration end depends not only on the source iterator, but also on the end condition; once the condition flips, the overall pipeline iterator is exhausted and can never be re-activated again (unless some special trickery is done by conspiring with the data source)
Definition at line 943 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
As in all the previously demonstrated cases, also the filtering is added as decorator, wrapping the source and all previously attached decoration layers. And in a similar way, various kinds of functors can be bound, and will be adapted automatically to work as a predicate to approve the elements to yield.
Definition at line 979 of file iter-explorer-test.cpp.
References lib::explore(), steam::mobject::session::test::anonymous_namespace{scope-query-test.cpp}::filter(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
Definition at line 1088 of file iter-explorer-test.cpp.
References lib::explore().
|
inlineprivate |
Definition at line 1177 of file iter-explorer-test.cpp.
References lib::explore(), and stage::widget::anonymous_namespace{element-box-widget.cpp}::reduce().
|
inlineprivate |
Definition at line 1219 of file iter-explorer-test.cpp.
References lib::explore(), and steam::mobject::session::test::anonymous_namespace{scope-query-test.cpp}::filter().
|
inlineprivate |
Definition at line 1235 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
The builder operations on IterExplorer each generate a distinct, implementation defined type, which is meant to be captured by auto
. However, the terminal builder function asIterSource()
moves the whole compound iterator object, as generated by preceding builder steps, into a heap allocation and exposes a simplified front-end, which is only typed to the result value type. Obviously, the price to pay comes in terms of virtual function calls for iteration, delegating to the pipeline backend.
IterSource<VAL>
is polymorphic and can be reassigned at runtime with an entirely different pipeline.IterSource<T>::iterator
, to expose the expandChildren()
operation. Definition at line 1270 of file iter-explorer-test.cpp.
|
inlineprivate |
Contrary to the preceding test case, here the point is to base the whole pipeline on a data source accessible through the IterSource (VTable based) interface. The notable point with this technique is the ability to use some extended sub interface of IterSource and to rely on this interface to implement some functor bound into the IterExplorer pipeline. Especially this allows to delegate the "child expansion" through such an interface and just return a compatible IterSource as result. This way, the opaque implementation gains total freedom regarding the concrete implementation of the "child series" iterator. In fact, it may even use a different implementation on each level or even on each individual call; only the result type and thus the base interface need to match.
Definition at line 1339 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
This is a simple extension where all elements are expanded automatically. In fact, the expandChildren()
operation implies already an iteration step, namely to dispose of the parent element before injecting the expanded child elements. Based on that observation, when we just replace the regular iteration step by a call to expandChildren()
, we'll encounter first the parent element and then delve depth-first into exploring the children.
Definition at line 1442 of file iter-explorer-test.cpp.
References lib::explore(), and lib::test::anonymous_namespace{iter-explorer-test.cpp}::materialise().
|
inlineprivate |
expandChildren()
function is actually triggered, whenever we've found a valid match on the current level. The (random) data source was chosen such as to make it very likely to find a match eventually, but also to produce some partial matches followed by backtrackingdepth()
information exposed on the opaque data source to react on navigation into nested scopes: here, we use this feature to create a protocol of the search to indicate the actual "winning path" Definition at line 1499 of file iter-explorer-test.cpp.
References lib::explore().