From 05e6e7f316348644cf2923065b644414b2dbc798 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 15 Sep 2018 03:07:33 +0200 Subject: [PATCH] ChainSearch: remould construction to get the logic correct on closer investigation it turned out that the logic of the first design attempt was broken altogether. It did not properly support backtracking (which was the reason to start this whole exercise) and it caused dangling references within the lambda closure once the produced iterator pipeline was moved out into the target location. Reasoning from first principles then indicated that the only sane way to build such a search evaluation component is to use *two* closely collaborating layers. The actual filter configuration and evaluation logic can not reside and work from within the expander. Rather, it must sit in a layer on top and work in a conventional, imperative way (with a while loop). Sometimes, functional programming is *not* the natural way of doing things, and we should then stop attempting to force matters against their nature. --- src/lib/iter-chain-search.hpp | 64 ++++----- src/lib/iter-tree-explorer.hpp | 22 +++- tests/library/iter-chain-search-test.cpp | 1 - wiki/thinkPad.ichthyo.mm | 160 ++++++++++++++++++++++- 4 files changed, 202 insertions(+), 45 deletions(-) diff --git a/src/lib/iter-chain-search.hpp b/src/lib/iter-chain-search.hpp index 3de00ddea..0c58798ce 100644 --- a/src/lib/iter-chain-search.hpp +++ b/src/lib/iter-chain-search.hpp @@ -75,13 +75,12 @@ namespace iter { .mutableFilter(); } - template + template auto - buildExplorer (SRC&& dataSource, FUN&& expandFunctor) + buildExplorer (SRC&& dataSource) { return buildSearchFilter (forward (dataSource)) - .expand (forward (expandFunctor)) - .expandAll(); + .expand ([](auto it){ return it; }); // child iterator starts as copy of current level iterator } /** @@ -97,16 +96,9 @@ namespace iter { struct _IterChainSetup { using Filter = decltype( buildSearchFilter(std::declval()).asIterator() ); - using StepFunctor = std::function; - - using Pipeline = decltype( buildExplorer (std::declval(), std::declval()) ); - - static Pipeline - configurePipeline (SRC&& dataSource, StepFunctor step) - { - return buildExplorer(forward (dataSource), move (step)); - } + using Pipeline = decltype( buildExplorer (std::declval()) ); + using StepFunctor = std::function; }; }//(End)type construction helpers @@ -134,9 +126,6 @@ namespace iter { using Filter = typename _Trait::Filter; using Step = typename _Trait::StepFunctor; - /** @internal access embedded filter sub-Pipeline */ - Filter& filter() { return *this; } - /** Storage for a sequence of filter configuration functors */ std::vector stepChain_; @@ -150,9 +139,7 @@ namespace iter { template explicit IterChainSearch (SEQ&& srcData) - : _Base{_Trait::configurePipeline (forward (srcData) - ,[this](Filter const& curr){ return configureFilterChain(curr); })} - , stepChain_{} + : _Base{buildExplorer (forward (srcData))} { // mark initial pristine state _Base::disableFilter(); } @@ -161,13 +148,28 @@ namespace iter { using _Base::_Base; + /* === adapted iteration control API === */ + void + iterNext() + { + _Base::__throw_if_empty(); + while (_Base::depth() < stepChain_.size() // Backtracking loop: attempt to establish all conditions + and _Base::checkPoint()) // possibly trying further combinations until success: + { + _Base::expandChildren(); // create copy of current filter embedded into child level + stepChain_[_Base::depth()] (_Base::accessCurrentChildIter()); // invoke step functor to reconfigure this filter... + _Base::dropExhaustedChildren(); // which thereby might become empty + } + } + + /** configure additional chained search condition. - * @param a functor `Filter const& -> filter`, which takes a current filter configuration, - * returning a copy from this configuration, possibly configured differently. + * @param a manipulation functor `void(Filter&)`, which works on the current filter + * to possibly change its configuration. * @note the given functor, lambda or function reference will be wrapped and adapted * to conform to the required function signature. When using a generic lambda, - * the argument type `Filter const&` is assumed + * the argument type `Filter&` is assumed * @remarks the additional chained search condition given here will be applied _after_ * matching all other conditions already in the filter chain. Each such condition * is used to _filter_ the underlying source iterator, i.e. pull it until finding @@ -185,7 +187,7 @@ namespace iter { Step nextStep{forward (configureSearchStep)}; if (_Base::isDisabled()) - this-> filter() = move (nextStep (*this)); // apply first step immediately + nextStep (*this); // apply first step immediately else { stepChain_.emplace_back (move (nextStep)); // append all further steps into the chain... @@ -207,10 +209,9 @@ namespace iter { search (FUN&& filterPredicate) { addStep ([predicate{forward (filterPredicate)}] - (Filter filter) // note: filter taken by value - { + (Filter& filter) + { // manipulte current filter configuration filter.setNewFilter (predicate); - return filter; // return copy of the original state with changed filter }); return move(*this); } @@ -239,17 +240,6 @@ namespace iter { _Base::disableFilter(); return move(*this); } - - private: - Filter - configureFilterChain (Filter const& currentFilterState) - { - uint depth = this->depth(); - if (depth < stepChain_.size()) - return stepChain_[depth](currentFilterState); // augmented copy - else - return Filter{}; // empty filter indicates recursion end - } }; diff --git a/src/lib/iter-tree-explorer.hpp b/src/lib/iter-tree-explorer.hpp index 47d9a9685..ebba4bbc4 100644 --- a/src/lib/iter-tree-explorer.hpp +++ b/src/lib/iter-tree-explorer.hpp @@ -722,12 +722,6 @@ namespace lib { or *expansions_; } - bool - hasChildren() const - { - return 0 < depth(); - } - void incrementCurrent() { @@ -737,12 +731,28 @@ namespace lib { ++(*this); } + + protected: + /** @internal accessor for downstream layers to allow close collaboration */ + ResIter& + accessCurrentChildIter() + { + REQUIRE (hasChildren()); + return *expansions_; + } + void dropExhaustedChildren() { while (not invariant()) ++expansions_; } + + bool + hasChildren() const + { + return 0 < depth(); + } }; diff --git a/tests/library/iter-chain-search-test.cpp b/tests/library/iter-chain-search-test.cpp index f8cca854a..bfd03e752 100644 --- a/tests/library/iter-chain-search-test.cpp +++ b/tests/library/iter-chain-search-test.cpp @@ -160,7 +160,6 @@ namespace test{ string currVal = *filter; filter.setNewFilter ([=](string const& val){ return val != currVal; }); - return filter; }); ///////////////////////////////////////////////////TODO WIP cout << materialise (search) < - + + + + + + + + + + + + + + + + + + + + + + +

+ ...nach der »reinen Lehre« +

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

+ ...insofern wir nämlich zwingend auf den jeweilign Kind-Iterator zugreifen müssen. +

+

+ Nicht nur auf den aktuellen Wert (=dereferenzierter Iterator, d.h. die Funktion yield()) +

+ + +
+ + +
+
@@ -32262,6 +32321,105 @@
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ weil ich dann auf dem IterChainSearch unmittelbar die builder-Funktionen definieren kann +

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

+ konfiguriert danach direkt den Filter +

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

+ ...weil der Basis-Iterator (also der Template-Parameter SRC) +

+

+ explizit und absichtlich einen anderen Typ haben könnte, als der expandierte Kind-Iterator. +

+

+ Das ist ein wesentliches Feature dieses Expander-Designs, würde aber leider eine +

+

+ komplett generische Accessor-Funktion unmöglich machen (das Template würde in +

+

+ einem Solchen Fall insgesamt vom Compiler zurückgewiesen) +

+ + +
+ +
+
+
+
+
+