Diff-Framework: resolve lurking problems with specific STL containers
basically the solution was a bit too naive and assumed everything is similar to a vector. It is not, and this leads to some insidious problems with std::map, which hereby are resolved by introducing ContainerTraits
This commit is contained in:
parent
8a5f1bc8d7
commit
806d569e06
5 changed files with 5559 additions and 4812 deletions
|
|
@ -51,7 +51,7 @@
|
|||
** On the other hand, this very notion of _ordering_ and _sequence_ is essential to the
|
||||
** meaning of "diff", as far as collections of "children" are involved. This leaves us
|
||||
** with the decision, either to increase complexity of the diff language's definition
|
||||
** and concept, absorb this discrepancy within the complexity of implementation.
|
||||
** and concept, or to accommodate this discrepancy within the binding implementation.
|
||||
** Deliberately, the whole concept of a "diff language" builds onto the notion of
|
||||
** _layered semantics,_ where the precise meaning of some terms remains a private
|
||||
** extension within specific usage context. There is a lot of leeway within the
|
||||
|
|
@ -84,6 +84,14 @@
|
|||
** constructing the concrete TreeMutator needs to have adequate understanding
|
||||
** regarding mode of operation and "mechanics" of such a binding.
|
||||
**
|
||||
** @remark for sake of completeness an alternative binding option should be mentioned:
|
||||
** Attributes could be represented as a map of `(key,value)` pairs and then bound
|
||||
** via the STL collection binding. This way, all the attributes of an "object"
|
||||
** would be treated as coherent unit, within a single "onion layer". However,
|
||||
** such a layout tends to run against the conventions and the protocol of the
|
||||
** diff language and should be confined to cover some corner cases (e.g. to
|
||||
** support an open ended collection of _custom properties_)
|
||||
**
|
||||
** @note the header tree-mutator-attribute-binding.hpp was split off for sake of readability
|
||||
** and is included automatically from bottom of tree-mutator.hpp
|
||||
**
|
||||
|
|
@ -116,7 +124,7 @@ namespace diff{
|
|||
/**
|
||||
* Generic behaviour of any binding to object fields (attributes).
|
||||
* Since object fields as such are part of the class definition, a diff
|
||||
* will never be able to add, insert, delted or re-order fields. Thus we
|
||||
* will never be able to add, insert, delete or re-order fields. Thus we
|
||||
* do not need to keep track of an "old" and "new" order; rather there
|
||||
* is always one single fixed element present to work on.
|
||||
* @note consequently, several diff operations are either implemented NOP,
|
||||
|
|
|
|||
|
|
@ -75,8 +75,75 @@ namespace diff{
|
|||
using lib::iter_stl::eachElm;
|
||||
|
||||
|
||||
/* === Technicalities of container access === */
|
||||
|
||||
template<class C>
|
||||
using _AsVector = std::vector<typename C::value_type>;
|
||||
template<class C>
|
||||
using _AsMap = std::map<typename C::key_type, typename C::mapped_type>;
|
||||
|
||||
template<class C>
|
||||
using IF_is_vector = lib::meta::enable_if< std::is_base_of<_AsVector<C>, C>>;
|
||||
template<class C>
|
||||
using IF_is_map = lib::meta::enable_if< std::is_base_of<_AsMap<C>, C>>;
|
||||
|
||||
|
||||
/** Helper for uniform treatment of various STL containers */
|
||||
template<class C, typename SEL =void>
|
||||
struct ContainerTraits
|
||||
{
|
||||
static_assert (not sizeof(C), "unable to determine any supported container type for C");
|
||||
};
|
||||
|
||||
template<typename V>
|
||||
struct ContainerTraits<V, IF_is_vector<V> >
|
||||
{
|
||||
using Vec = _AsVector<V>;
|
||||
using Elm = typename Vec::value_type;
|
||||
using Itr = typename Vec::iterator;
|
||||
|
||||
static Itr
|
||||
recentElmRawIter (Vec& vec)
|
||||
{
|
||||
return Itr{&vec.back()};
|
||||
}
|
||||
|
||||
static void
|
||||
append (Vec& vec, Elm&& elm)
|
||||
{
|
||||
vec.emplace_back (forward<Elm> (elm));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename M>
|
||||
struct ContainerTraits<M, IF_is_map<M> >
|
||||
{
|
||||
using Map = _AsMap<M>;
|
||||
using Key = typename Map::key_type;
|
||||
using Val = typename Map::mapped_type;
|
||||
using Elm = std::pair<const Key, Val>;
|
||||
|
||||
/** heuristic for `std::map`: lookup via reverse iterator.
|
||||
* Since std::map iterates in key order, the most recently inserted
|
||||
* element is likely also the largest element. If this guess fails,
|
||||
* there will always be a second try by searching over all elements.
|
||||
*/
|
||||
static auto
|
||||
recentElmRawIter (Map& map)
|
||||
{
|
||||
auto& recentPos = ++map.rend();
|
||||
return map.find (recentPos->first);
|
||||
}
|
||||
|
||||
static void
|
||||
append (Map& map, Elm&& elm)
|
||||
{
|
||||
map.emplace (forward<Elm> (elm));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Attach to collection: Concrete binding setup.
|
||||
* This record holds all the actual binding and closures
|
||||
|
|
@ -101,6 +168,7 @@ namespace diff{
|
|||
{
|
||||
using Coll = typename Strip<COLL>::TypeReferred;
|
||||
using Elm = typename Coll::value_type;
|
||||
using Trait = ContainerTraits<Coll>;
|
||||
|
||||
using iterator = typename lib::iter_stl::_SeqT<Coll>::Range;
|
||||
using const_iterator = typename lib::iter_stl::_SeqT<const Coll>::Range;
|
||||
|
|
@ -150,7 +218,7 @@ namespace diff{
|
|||
void
|
||||
inject (Elm&& elm)
|
||||
{
|
||||
emplace (collection, forward<Elm>(elm));
|
||||
Trait::append (collection, forward<Elm>(elm));
|
||||
}
|
||||
|
||||
iterator
|
||||
|
|
@ -161,11 +229,13 @@ namespace diff{
|
|||
return pos;
|
||||
}
|
||||
|
||||
/** locate element for assignment or mutation,
|
||||
* with special shortcut to the recently inserted element */
|
||||
iterator
|
||||
locate (GenNode const& targetSpec)
|
||||
{
|
||||
if (not collection.empty()
|
||||
and matches (targetSpec, recentElm(collection)))
|
||||
and matches (targetSpec, recentElm()))
|
||||
return recentElmIter();
|
||||
else
|
||||
return search (targetSpec, eachElm(collection));
|
||||
|
|
@ -182,65 +252,13 @@ namespace diff{
|
|||
iterator
|
||||
recentElmIter()
|
||||
{
|
||||
return iterator (recentElmRawIter(collection), collection.end());
|
||||
return iterator{Trait::recentElmRawIter (collection), std::end (collection)};
|
||||
}
|
||||
|
||||
template<class C>
|
||||
static auto
|
||||
recentElmRawIter (C& coll) ///< fallback: use first element of container
|
||||
Elm&
|
||||
recentElm()
|
||||
{
|
||||
return coll.begin();
|
||||
}
|
||||
|
||||
template<typename E>
|
||||
using Map = std::map<typename E::key_type, typename E::second_type>;
|
||||
|
||||
template<typename E>
|
||||
static auto
|
||||
recentElmRawIter (Map<E>& map) ///< workaround for `std::Map`: lookup via reverse iterator
|
||||
{
|
||||
return map.find (recentElm(map).first);
|
||||
}
|
||||
|
||||
static auto
|
||||
recentElmRawIter (std::vector<Elm>& vec)
|
||||
{
|
||||
return typename std::vector<Elm>::iterator (&vec.back());
|
||||
}
|
||||
|
||||
|
||||
template<class C>
|
||||
static Elm&
|
||||
recentElm (C& coll)
|
||||
{
|
||||
return *coll.begin();
|
||||
}
|
||||
|
||||
template<typename E>
|
||||
static E&
|
||||
recentElm (Map<E>& map)
|
||||
{
|
||||
return *++map.rend();
|
||||
}
|
||||
|
||||
static Elm&
|
||||
recentElm (std::vector<Elm>& vec)
|
||||
{
|
||||
return vec.back();
|
||||
}
|
||||
|
||||
|
||||
template<class C>
|
||||
static void
|
||||
emplace (C& coll, Elm&& elm)
|
||||
{
|
||||
coll.emplace (forward<Elm> (elm));
|
||||
}
|
||||
|
||||
static void
|
||||
emplace (std::vector<Elm>& coll, Elm&& elm)
|
||||
{
|
||||
coll.emplace_back (forward<Elm> (elm));
|
||||
return *Trait::recentElmRawIter (collection);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ namespace test{
|
|||
{
|
||||
buff.create (
|
||||
TreeMutator::build()
|
||||
.attach (collection (static_cast<vector<string>&> (*this))
|
||||
.attach (collection (*this)
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9690,7 +9690,7 @@ attached to a clip, or the mixture of clips, effects and labels found within a [
|
|||
You might have noticed already that this entire design is recursive: there is some part, where -- for handling a sub-structure -- a nested mutator is fabricated and placed into a given buffer. And only after understanding that part, it becomes clear to what end we are creating a customised mutator: we always need some (relative) parent scope, which happens to know more about the actual data to be treated with a TreeMutator. This scope assists with creating a suitable binding. Obviously, from within that binding, it is known what the sub-structures and children of that local data are all about and what semantics to give to the fundamental operations.
|
||||
</pre>
|
||||
</div>
|
||||
<div title="TreeMutatorEvolution" creator="Ichthyostega" modifier="Ichthyostega" created="201603160255" modified="201912061709" tags="Model Concepts design draft" changecount="37">
|
||||
<div title="TreeMutatorEvolution" creator="Ichthyostega" modifier="Ichthyostega" created="201603160255" modified="201912140026" tags="Model Concepts design draft" changecount="38">
|
||||
<pre>The TreeMutator is an intermediary to translate a generic structure pattern into heterogeneous local invocation sequences.
|
||||
This page details some of the reasoning and documents decisions taken while shaping the chosen design.
|
||||
|
||||
|
|
@ -9845,7 +9845,10 @@ So we arrive a the following guidelines:
|
|||
|
||||
!!!types, attributes and layering
|
||||
During the analysis it turned out that support for a mixed set of //typed child elements// is an important goal. However, we may impose the restriction that these typed sub collections will be kept segregated and not be mixed up within the child scope. Effectively, this was already the fundamental trait for the //symbolic object representation// chosen here. Thus, "attributes" become just one further kind of typed children; even "metadata" might be treated along these lines in future (currently, as of 2016 we have only one single piece of metadata, the type field in {{{diff::Record}}}, which is treated explicitly in the code).
|
||||
This observation leads directly to an implementation based on layered decorators. Each kind of target elements will be treated by another decorator exclusively, and control is passed through a chain of such decorators. This means, each decorator (or "onion-layer") gets into charge based on a //selector predicate,// which works on the given diff verb solely (no introspection and no "peeking" into the implementation data). We should note though, that this implies some fine points of the diff language semantics are subject to this architectural decision -- especially the precise behaviour of the {{{AFTER}}}-verb depends on the //concrete layer structure// used at the target of the diff. Yet this seems acceptable, since it is the very nature of diff application to be dependent on internals of the target location -- just by definition we assume this inner structure of the target to be semantically congruent with the diff's source structure.
|
||||
This observation leads directly to an implementation based on layered decorators. Each kind of target elements will be treated by another decorator exclusively, and control is passed through a chain of such decorators. Consequently, each decorator (or "onion-layer") gets into charge based on a //selector predicate,// which works on the given diff verb solely (no introspection and no "peeking" into the implementation data). We should note though, that this implies some fine points of the diff language semantics are subject to this architectural decision -- especially the precise behaviour of the {{{AFTER}}}-verb depends on the //concrete layer structure// used at the target of the diff. Yet this seems acceptable, since it is the very nature of diff application to be dependent on internals of the target location -- just by definition we assume this inner structure of the target to be semantically congruent with the diff's source structure.
|
||||
|
||||
!!!an alternative approach to »attributes«
|
||||
In the end, the implementation resulting from this layered decorator solution leads to another, alternative solution to deal with //object properties:// using an ''Attribute Map''. Obviously, this solution is in direct contrast to the model of an object field as defined through the //object's class.// However, sometimes, some entities in practice will support an //open ended collection of optional custom properties// -- and this is where the attribute map model shines. And it should be noticed that the implementation of the attribute map binding comes basically for free, once we have a standard binding in place to handle STL collections. There is only one technical discrepancy to bridge: a mapping container might be ordered (and in fact {{{stl::map}}} is, implemented as red-black tree); an intrinsic ordering of the container's contents is in contradiction to the basic concept of the diff framework, which assumes there is an explicit order, which can be changed by fetching and inserting some elements. And thus, this alternative representation of »attributes« should not be pushed too far.
|
||||
|
||||
|
||||
!Implementation
|
||||
|
|
|
|||
10216
wiki/thinkPad.ichthyo.mm
10216
wiki/thinkPad.ichthyo.mm
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue