Implementation skeleton for advice binding match

This commit is contained in:
Fischlurch 2010-04-12 05:04:27 +02:00
parent e3c963378f
commit f27024172f
5 changed files with 254 additions and 31 deletions

View file

@ -0,0 +1,71 @@
/*
Binding - pattern defining a specific attachment to the Advice system
Copyright (C) Lumiera.org
2010, 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.
* *****************************************************/
#include "lib/advice/binding.hpp"
//#include <tr1/functional_hash.h>
namespace lib {
namespace advice {
// using std::tr1::hash;
// LUMIERA_ERROR_DEFINE (MISSING_INSTANCE, "Existing ID registration without associated instance");
/* ohlolololohaha */
Binding::Binding ()
{
UNIMPLEMENTED ("create a new empty binding");
}
Binding::Binding (Literal spec)
{
UNIMPLEMENTED ("parse the spec and create a new binding");
}
void
Binding::addPredicate (Literal spec)
{
UNIMPLEMENTED ("parse the given spec and create an additional predicte, then re-normalise");
}
Binding::operator string() const
{
UNIMPLEMENTED ("diagnostic string representation of an advice binding");
}
HashVal
Binding::calculateHash() const
{
UNIMPLEMENTED ("calculate the hash for a normalised advice binding");
}
}} // namespace lib::advice

View file

@ -70,7 +70,7 @@
//#include <boost/operators.hpp>
//#include <tr1/memory>
//#include <iostream>
#include <iostream>
#include <string>
namespace lib {
@ -78,6 +78,9 @@ namespace advice {
using std::string;
typedef size_t HashVal;
/**
* Conjunction of predicates to be matched
@ -87,30 +90,150 @@ namespace advice {
*/
class Binding
{
public:
/** create the empty binding, equivalent to \c true */
Binding();
/** create the binding as defined by the given textual definition.
* @note implicit type conversion deliberately intended */
Binding (Literal spec);
/*-- Binding is default copyable --*/
/** extend the definition of this binding
* by adding a predicate according to the
* given textual definition */
void addPredicate (Literal spec);
template<typename TY>
void addTypeGuard();
operator string() const;
public:
/**
* Functor object for matching against another Binding.
* Contains precompiled information necessary for
* determining a match.
*/
class Matcher
{
HashVal bindingHash_;
Matcher (HashVal ha)
: bindingHash_(ha)
{ }
friend class Binding;
public:
bool matches (Binding const& obi) const;
bool matches (Binding::Matcher const& oma) const;
friend HashVal hash_value (Matcher const&);
};
/** create the empty binding, equivalent to \c true */
Binding();
/** create the binding as defined by the given textual definition.
* @note implicit type conversion deliberately intended */
Binding (Literal spec);
/*-- Binding is default copyable --*/
/** extend the definition of this binding
* by adding a predicate according to the
* given textual definition */
void addPredicate (Literal spec);
template<typename TY>
void addTypeGuard();
Matcher buildMatcher() const;
HashVal calculateHash() const;
operator string() const;
private:
void normalise(); ////TODO necessary??
};
////TODO define the hash function here, to be picked up by ADL
inline std::ostream&
operator<< (std::ostream& os, Binding const& bi)
{
return os << string(bi);
}
template<typename TY>
inline void
Binding::addTypeGuard()
{
UNIMPLEMENTED ("create a new predicate spec to denote that this binding is related to the given advice type");
}
/* === equality comparison and matching === */
/** bindings are considered equivalent if, after normalisation,
* their respective definitions are identical.
* @note for bindings without variable arguments, equivalence and matching
* always yield the same results. Contrary to this, two bindings with
* some variable arguments could match, without being defined identically.
* For example \c pred(X) matches \c pred(u) or any other binding of the
* form \c pred(<constant_value>)
*/
inline bool
operator== (Binding const& b1, Binding const& b2)
{
UNIMPLEMENTED ("equality of bindings, based on term by term comparison");
}
inline bool
operator!= (Binding const& b1, Binding const& b2)
{
return ! (b1 == b2);
}
inline bool
matches (Binding const& b1, Binding const& b2)
{
return b1.buildMatcher().matches (b2);
}
inline bool
matches (Binding::Matcher const& m1, Binding::Matcher const& m2)
{
return m1.matches (m2);
}
inline Binding::Matcher
Binding::buildMatcher() const
{
return Matcher (this->calculateHash());
}
/* == access hash values used for matching == */
inline bool
Binding::Matcher::matches (Binding const& obi) const
{
return bindingHash_ == obi.calculateHash();
}
inline bool
Binding::Matcher::matches (Binding::Matcher const& oma) const
{
return bindingHash_ == hash_value(oma);
}
inline HashVal
hash_value (Binding::Matcher const& bm)
{
return bm.bindingHash_;
}
inline HashVal
hash_value (Binding const& bi)
{
return bi.calculateHash();
}

View file

@ -102,7 +102,7 @@ namespace asset {
inline ostream&
operator<< (ostream& os, const Category& cat)
operator<< (ostream& os, Category const& cat)
{
return os << string(cat);
}

View file

@ -35,6 +35,8 @@
#include "lib/time.h"
//#include "lib/symbol.hpp"
//#include <tr1/functional_hash.h>
//#include <boost/functional/hash.hpp>
#include <iostream>
//#include <string>
@ -48,6 +50,7 @@
//using lib::Symbol;
//using lumiera::P;
//using std::string;
//using boost::hash;
using std::cout;
using std::endl;
@ -87,6 +90,7 @@ namespace test {
{
verifyPatternNormalisation();
verifyStaticMatch();
verifyPreparedMatch();
verifyDynamicMatch();
}
@ -94,10 +98,9 @@ namespace test {
void
verifyPatternNormalisation()
{
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #605
Binding b0, b00;
Binding b1 ("cat1(), cat2().");
Binding b2 (" cat2 ( ),cat1( ) . ");
Binding b2 (" cat2 ( ),cat1 . ");
CHECK (b0 == b00); CHECK (b00 == b0);
CHECK (b1 == b2); CHECK (b2 == b1);
@ -120,14 +123,12 @@ namespace test {
cout << "b0==" << b0 << endl;
cout << "b1==" << b1 << endl;
cout << "b2==" << b2 << endl;
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #605
}
void
verifyStaticMatch()
{
#if false /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #605
CHECK ( matches (Binding(), Binding()));
CHECK ( matches (Binding("pred()"), Binding("pred( ) ")));
@ -137,7 +138,35 @@ namespace test {
CHECK ( matches (Binding("pred(x), pred(y)"), Binding("pred(y), pred(x)")));
CHECK (!matches (Binding("pred(x), pred(y)"), Binding("pred(y), pred(y)")));
#endif /////////////////////////////////////////////////////////////////////////////////////////////////////////////UNIMPLEMENTED :: TICKET #605
}
void
verifyPreparedMatch()
{
Binding b1 ("pred()");
Binding b2 ("pred");
Binding b3 ("pred, pred(x)");
Binding b4 ("pred ( x ) , pred().");
CHECK ( matches (b1,b2));
CHECK ( matches (b3,b4));
Binding::Matcher bm1 (b1.buildMatcher());
Binding::Matcher bm2 (b2.buildMatcher());
Binding::Matcher bm3 (b3.buildMatcher());
Binding::Matcher bm4 (b4.buildMatcher());
CHECK (hash_value(b1) == hash_value(bm1));
CHECK (hash_value(b2) == hash_value(bm2));
CHECK (hash_value(b3) == hash_value(bm3));
CHECK (hash_value(b4) == hash_value(bm4));
CHECK (hash_value(b1) != hash_value(b3));
CHECK ( matches (bm1,bm2));
CHECK ( matches (bm3,bm4));
CHECK (!matches (bm1,bm3));
CHECK (!matches (bm2,bm4));
}

View file

@ -541,7 +541,7 @@ In a more elaborate scheme, the advised entity could provide a signal to be invo
&amp;rarr; AdviceImplementation
</pre>
</div>
<div title="AdviceImplementation" modifier="Ichthyostega" modified="201004102033" created="201004100056" tags="impl draft img" changecount="14">
<div title="AdviceImplementation" modifier="Ichthyostega" modified="201004110525" created="201004100056" tags="impl draft img" changecount="17">
<pre>[&lt;img[Advice solution|uml/fig141573.png]]
@ -550,15 +550,15 @@ In a more elaborate scheme, the advised entity could provide a signal to be invo
The advice system is //templated on the advice type// &amp;mdash; so basically there is an independent lookup table for each different kind of advice.The advice system is a sytem wide singleton service, but it is never addressed directly by the participants. Rather, instances of ~AdviceProvision and ~AdviceRequest act as point of access. But these aren't completely symmetric; while the ~AdviceRequest is owned by the advised entity, the ~AdviceProvision is a value object, a uniform holder used to introduce new advice into the system. ~AdviceProvision is copied into an internal buffer and managed by the advice system, as is the actual advice item, which is copied alongside.
In order to find matches and provide advice solutions, the advice system maintains an index data structure called ''~BindingIndex''. The actual binding predicates are represented by value objects stored within this index table. The matching process is triggered whenever a new possibility for an advice solution enters the system, which could be a new request, a new provision or a change in the specified bindings. A successful match causes a pointer to be set within the ~AdviceRequest, pointing to the ~AdviceProvision acting as solution. Thus, when a solution exists, the advised entity can access the advice value object by dereferencing this pointer. A new advice solution just results in setting a different pointer, which is atomic and doesn't need to be protected by locking. But note, when omitting locking there is no memory barrier; thus the advised might not see a changed advice solution, until the corresponding thread(s) refresh their cpu cache. This might or might not be acceptable, depending on the context, and thus is configurable as policy. Similarily, the handling of default advice is configurable. Usually, advice is a default constructible value object. In this case, when there isn't any advice solution (yet), a pseudo solution holding the default constructed advice value is used to satisfy any advice access by the client (advised entity). The same can be used when the actual ~AdviceProvision gets //retracted.// As an alternative, when this default solution approach doesn't work, we can provide a policy either to throw or to wait blocking &amp;mdash; but this alternative policy is similarily implemented with an //null object// (a placeholder ~AdviceProvision). Anyway, this implementation techinque causes the advice system to collect some advice provisions, bindings and advice objects over time. It should use a pooling custom allocator in the final version. As the number of advisors is expected to be rather small, the storage occupied by these elements, which is effectively blocked until application exit, isn't considered a problem.
In order to find matches and provide advice solutions, the advice system maintains an index data structure called ''~BindingIndex''. The actual binding predicates are represented by value objects stored within this index table. The matching process is triggered whenever a new possibility for an advice solution enters the system, which could be a new request, a new provision or a change in the specified bindings. A successful match causes a pointer to be set within the ~AdviceRequest, pointing to the ~AdviceProvision acting as solution. Thus, when a solution exists, the advised entity can access the advice value object by dereferencing this pointer. A new advice solution just results in setting a different pointer, which is atomic and doesn't need to be protected by locking. But note, omitting the locking means there is no memory barrier; thus the advised entity might not see any changed advice solution, until the corresponding thread(s) refresh their CPU cache. This might or might not be acceptable, depending on the context, and thus is configurable as policy. Similarly, the handling of default advice is configurable. Usually, advice is a default constructible value object. In this case, when there isn't any advice solution (yet), a pseudo solution holding the default constructed advice value is used to satisfy any advice access by the client (advised entity). The same can be used when the actual ~AdviceProvision gets //retracted.// As an alternative, when this default solution approach doesn't work, we can provide a policy either to throw or to wait blocking &amp;mdash; but this alternative policy is similarly implemented with an //null object// (a placeholder ~AdviceProvision). Anyway, this implementation technique causes the advice system to collect some advice provisions, bindings and advice objects over time. It should use a pooling custom allocator in the final version. As the number of advisors is expected to be rather small, the storage occupied by these elements, which is effectively blocked until application exit, isn't considered a problem.
!organising the advice solution
This is the tricky part of the whole advice system implementation. A naive implementation will quickly degenerate in performance, as costs are of order ~AdviceProvisions * ~AdviceRequests * (average number of binding terms). But contrary to the standard solutions for rules based systems (either forward or backward chaining), in this case here always complete binding sets are to be matched, which allows to reduce the effort.
!!!solution idea
The binding patterns are organised by //predicate symbol and the lists are normalised.// A simple normalisation could be lexicographic ordering of the predicate symbols. Then the resulting representation can be //hashed.// When all predicates are constant, match can be found by hashtable lookup, otherwise, in case some predicates contain variable arguments ({{red{planned extension}}}), the lookup is followed by an unification.
The binding patterns are organised by //predicate symbol and the lists are normalised.// A simple normalisation could be lexicographic ordering of the predicate symbols. Then the resulting representation can be //hashed.// When all predicates are constant, match can be found by hashtable lookup, otherwise, in case some predicates contain variable arguments ({{red{planned extension}}}), the lookup is followed by an unification. For this to work, we'll have to include the arity into the predicate symbols used in the first matching stage. Moreover, we'll create a //matching closure// (functor object), internally holding the arguments for unification. This approach allows for //actual interpretation of the arguments.// It is conceivable that in special cases we'll get multiple instances of the same predicate, just with different arguments. The unification of these terms needs to consider each possible pairwise combination (cartesian product) &amp;mdash; but working out the details of the implementation can safely be deferred until we'll actually hit such a special situation, thanks to the implementation by a functor.
Yet still we need to store a successful match, together with backlinks, in order to handle changing and retracting of advice.
Fortunately, the calculation of this normalised patterns can be separated completely from the actual matching. Indeed, we don't even need to store the binding patterns at all within the binding index &amp;mdash; storing the hash value is sufficient (and in case of patterns with arguments we'll attach the matching closure functor). Yet still we need to store a marker for each successful match, together with back-links, in order to handle changing and retracting of advice.
</pre>
</div>
<div title="AdviceRequirements" modifier="Ichthyostega" modified="201004100149" created="201004060213" tags="design impl" changecount="17">