Generators
renatocf opened this issue · 24 comments
Hey @igorbonadio,
I was thinking about ToPS' design and our ideas of having an Evaluator
(storage of data and interface for inference methods) and a Trainer
(builder to create models bit by bit). In this way, methods choose
still would be accessed by model classes. So, what do you think about creating a class Generator
, which chooses a new sequence for a given model? I don't think it's as useful as evaluators and trainers, but it would be, at least, symmetric in all uses.
A short example with this intermediate classes:
auto hmm = HiddenMarkovModel::trainer(training_set)->trainML(/* training arguments */);
auto labeling = hmm->decodableEvaluator(sequence)->labeling(Labeling::bestPath);
auto newSequence = hmm->generator()->choose(size, phase);
What do you think?
I think it is a good idea! That way, a model will only define its structure.
Evaluator knows how to infer and Generator knows how to generate data. Let's do it.
This classes will be very similar to Evaluator
. However, I think we have to solve some problems before. For example: as I looked the choose
methods in our model classes, we had the following signature:
virtual Symbol choosePosition(const Sequence &s, unsigned int i,
unsigned int phase = 0) const;
virtual Sequence chooseSequence(Sequence &s, unsigned int size,
unsigned int phase = 0) const;
I believe we need to do some refactorings:
- Remove the argument
s
fromchoosePosition
. I'm not sure about this one, but in all implementations (all that are not missing), it's never used. - Remove the argument
s
fromchooseSequence
. The sequence is returned twice - once by the argument and another time by the return value. And I believe the latter is more clear. - Rename both methods to
choose
. If sequences
is really necessary inchoosePosition
, we'll have different parameters and will be able to use polimorphism.
But, before anything, we need to test all methods choose
. And I'm not sure how we can do that. I downloaded and compiled ToPS 1.0, but I didn't find an app to test sequence generation (and I don't know how to test them on hand). So I believe we should start by that.
s
is used in VLMC.- As
chooseSequence
depends onchoosePosition
,s
is used inchooseSequence
- I think
choosePosition
will behave the same way thatevaluatePosition
And I agree. It is better add test before we touch in this code... Take a look at the Random header. I think we need to change it a little, because the seed is always randomically generated... It should have a way to set a fixed seed.
I changed the default random number generator to Marsenne Twister. Accordingly to Wikipedia, it's the most used algorithm among programming languages. This implementation, different from default_random_engine
, is platform independent. I also implemented a function to reset the engine - which enable the tests to be independent. At least we have basic guarantees when refactoring the code.
Ah, and to clarify, this page explains how <random>
library works. This paragraph is specially interesting:
The choice of which engine to use involves a number of tradeoffs: the linear congruential engine is moderately fast and has a very small storage requirement for state. The lagged Fibonacci generators are very fast even on processors without advanced arithmetic instruction sets, at the expense of greater state storage and sometimes less desirable spectral characteristics. The Mersenne twister is slower and has greater state storage requirements but with the right parameters has the longest non-repeating sequence with the most desirable spectral characteristics (for a given definition of desirable).
I think Mersenne Twister has this advantage not listed there: it's platform independent. For our production code, what do you think it would be better to use?
Another thing: do you believe that, as VLMC uses the sequence s
, we could change the choose
methods signature from:
virtual Symbol choosePosition(const Sequence &s, unsigned int i,
unsigned int phase = 0) const;
virtual Sequence chooseSequence(Sequence &s, unsigned int size,
unsigned int phase = 0) const;
to:
virtual Symbol choose(const Sequence &s,
unsigned int i,
unsigned int phase = 0) const;
virtual Sequence choose(unsigned int size,
unsigned int phase = 0) const;
for all ProbabilisticModel
s?
I've already answered the previous question (and, further, changed the methods).
Now, I'm attempting to create a class DecodableEvaluator
, which will have nice stuff Following our philosophy of always return through objects (and never for non-const-references or pointers in parameters), I'd like to change chooseSequences
signature from:
virtual void chooseSequences(Sequence &xs,
Sequence &ys,
unsigned int size) const;
to:
virtual std::pair<Sequence, Sequence> choose(unsigned int size,
unsigned int phase = 0) const;
I believe a pair is enough, given knowing which sequence would be x
or y
is meaningless (at least as far as I understood). So, accessing .first
and .second
in the return value seems OK.
However, it creates a problem: we cannot have functions overloaded by return values... How do you think we could deal with that? Change the refactored name is the most obvious solution. But, which name should we choose? For example: jointProbabilityOf
would be a good name for the overloaded probabilityOf
in Evaluator
. Is there something similar to choose
? I mean, is there a special meaning in choosing two functions together?
- pair
virtual std::pair<Sequence, Sequence> choose(unsigned int size,
unsigned int phase = 0) const;
In fact, x
and y
are an observation and a label sequence. But I think pair is a generic and good solution.
- I think a good name could be
chooseJointSequence
. What do you think?
So, y
is a label sequence. But what type of label sequence? As in Labeling
? (which has Method::bestPath
and Method::posteriorDecoding
?). Because in this case, perhaps there is a better meaning for what this function generates. Do you know if there is any relation between these things?
It generates a sequence of observations (x) that has a segmentation (y)
@igorbonadio. After thinking about our last conversation, I created a new proposal for changes in the definition of Labeling
, Generator
and Evaluator
. Take a look and tell me what you think about it.
First, the basic probabilistic model methods would become:
// I would introduce the alias "Probability" to standardize
// the use of "double" and "float" among methods. It also
// increases the meaning of method signatures
virtual Probability evaluate(const Sequence &sequence,
unsigned int pos,
unsigned int phase = 0) const;
virtual Symbol choose(const Sequence &context,
unsigned int pos,
unsigned int phase = 0) const;
virtual Sequence choose(unsigned int size,
unsigned int phase = 0) const;
Then, for decodable models, we can define a sequence + label version:
virtual Probability evaluateLabeling(const Labeling<Sequence> &sequence,
unsigned int i) const;
virtual Labeling<Symbol> chooseLabeling(const Sequence &context,
unsigned int pos) const;
virtual Labeling<Sequence> chooseLabeling(unsigned int size) const;
In this way, Labeling
would become a simple template class that would have the following methods:
template <typename T>
class Labeling {
private:
bool has_probability;
T observation;
T label;
Probability probability;
public:
const T& observation() const;
const T& label() const;
bool has_probability() const;
Probability probability() const;
}
So, a Labeling
is the association of an observation
, a label
and its joint probability
(if it exists). I'm not sure what is the best method for testing if there is a probability - or if we could have an EvaluatedLabeling
/Estimated<Labeling>
, subclass/decorator of Labeling
(just throwing some ideas). Anyway, what do you thing of this general design?
I thing it is great. But why is Labeling a template class?
I thought it would be better because we can specify if we have a labeling of a symbol or a labeling of a sequence (as they are different things, and I believe we should have different constraints in each of them).
What do you think about the idea of an Estimation
class? It could have two attributes - a probability
and an estimate
- in a similar way of labeling. It would be also a template, and they would work in a way similar to having "decorators" around our class (if the language was dynamic, I'd implement it delegating all methods in an incremental way. However, I see no easy way of doing it in C++).
The implementation would be something like this:
template<typename T>
class Labeling {
private:
T observation;
T label;
public:
T& observation();
T& label();
}
template<typename T>
class Estimation {
private:
T estimated;
Probability probability;
public:
T& estimated();
T& probability();
}
So, return values would be something like:
virtual Estimation<Labeling<Symbol>> chooseLabeling(const Sequence &context,
unsigned int pos) const;
virtual Estimation<Labeling<Sequence>> chooseLabeling(unsigned int size) const;
They would be used as this:
auto generator = hmm->generator();
auto estimation = generator->chooseLabeling(some_size);
estimation.probability();
estimation.estimated().label();
estimation.estimated().observation();
What do you think? Is that a lot? I'm not sure if we need all of this. I'll start implementing the refactorings I proposed in the last comment, and then e can see what is better.
Hummm It seems interesting. And I like how to code looks
estimation.probability();
estimation.estimated().label();
estimation.estimated().observation();
Nice. Perhaps I'll try to find more interesting names (I specially didn't like estimated
), but it'll be very similar to this.
I decided to do a small TODO list to help me to organize myself:
- Create alias
Probability
for method definitions. - Change definition of
Labeling
to a templateLabeling<T>
. - Add sequence
observation
in the definition ofLabeling<T>
. - Remove probability
probability
in the definition ofLabeling<T>
. - Create template decorator
Estimation<T>
withprobability
parameter. - Rename method
chooseSequences
tochooseSequenceLabeling
. - Rename method
chooseSequencesPosition
tochooseSymbolLabeling
. - Rename method
choose
(formerchooseSequence
) tochooseSequence
. - Rename method
choose
(formerchoosePosition
) tochooseSymbol
. - Create class
DecodableGenerator
inheriting fromGenerator
. - Rename method
evaluate
(formerevaluateSequences
) toevaluateLabeling
.
I was thinking about the design of Generator
's. Right now, we planned to have just one method generator
that would give us an instance of Generator
class, with methods to choose standalone/labeled symbols/sequences. Instead of that, I though it could be nice to have a slightly different design: in ProbabilisticModel
, we'd have a method sequenceGenerator
, which would give us a Generator<Sequence>
with a method choose
. At the same time, all subclasses of DecodableModel
would also have a labelingGenerator
that returns a Generator<Labeling>
with an equal method choose
. The usage would then be:
hmm->sequenceGenerator()->choose(size, phase);
hmm->labelingGenerator()->choose(size, phase);
The problem that arises from that is: how can the templates make their double dispatch and delegate to the right class methods? Perhaps I could create specializations for Sequence and Labeling. This is an alternative to inheritance. What do you think is the best alternative?
Better than my last suggestion: the lazy template method generation solve the problem. We just need to check (as in SimpleEvaluatorImpl
and CachedEvaluatorImpl
) our trait is_decodable<T>
:
If we have the class:
template<typename Model>
class Generator {
Sequence chooseSequence(unsigned int size, unsigned int phase) {
/* choose sequence */
}
Labeling chooseLabeling(unsigned int size, unsigned int phase) {
return chooseLabelingImpl(size, phase);
}
template<typename T = Model>
Labeling chooseLabelingImpl(unsigned int size, unsigned int phase,
not_decodable<T>* dummy) {
/* throw error */
}
template<typename T = Model>
Labeling chooseLabelingImpl(unsigned int size, unsigned int phase,
is_decodable<T>* dummy) {
/* create labeling */
}
}
Here it is the reformulated version of the minimal example I showed you earlier. As this example looked very nice. And I think it give us a behavior very similar to a client-server architecture.
Basically, Foo
, FooImpl
, SimpleFooImpl
and CachedFooImpl
are the implementation of a front-end for using back-end classes Bar
, BarDerived
and BarReusing
. In order to use method
, first we get an instance of the Foo
front-end from one of the back-ends. All state is kept in the front-end, while execution is made only in the back-end.
The trickiest thing is that we have a composite pattern in the back-end and a bridge pattern in the front-end: the first because of the nature of the problem we're solving (integrator probabilistic models behavior like a composite) and the second because it's the only way we can generate code accordingly to the classes (a workaround for not having virtual template functions). Finally, both things communicate in a client-server way. Nevertheless, in the end of the day, they're just a bunch of design patterns together.
Now, to make things easier, I listed how methods are called from main
until algorithms are executed:
////////////////////////////////////////////////////////////////////////////////
// //
// foo = model->foo() //
// //
////////////////////////////////////////////////////////////////////////////////
// //
// foo->method() //
// \/ //
// fooImpl->method() //
// \/ //
// fooImpl->methodImpl() //
// \/ //
// model->simpleMethod(fooImpl) //
// \/ //
// model->simpleMethodImpl(fooImpl) //
// //
////////////////////////////////////////////////////////////////////////////////
And here it's the working code:
// Standard headers
#include <memory>
#include <iostream>
#include <type_traits>
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
IMPLEMENTATION HELPER
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* MACRO GENERATE_HAS_MEMBER **************************************************/
// This macro is aimed to create a member detector: a class and type trait
// that can be used to check when an attribute/method exists in a class.
// As there is an infinite number of names, it's impossible to create a
// standard type trait only with the resources the language provides. Given
// that, it's necessary to create a macro to automatically generate all
// classes and alias from a name given as parameter.
// The following macro creates:
// - A template class that uses SFINAE and multiple inheritance to decide if
// the member exists in the class. In case it does, the inner class `Derived`
// has two `member`s and its size is the same is a char[2]. This information
// is to store in `RESULT` a boolean that indicates a member exists.
// - A struct inheriting from `std::integral_constant`, which have a trait
// compliant with STL.
// - Two alias `has_##member` and `no_##member` to selectively create
// methods by applying SFINAE on its parameters.
#define GENERATE_HAS_MEMBER(member) \
\
template <typename T> \
class HasMember_##member \
{ \
private: \
using Yes = char[2]; \
using No = char[1]; \
\
struct Fallback { int member; }; \
struct Derived : T, Fallback { }; \
\
template<typename U> static No& test (decltype(U::member)*); \
template<typename U> static Yes& test (U*); \
\
public: \
static constexpr bool RESULT \
= sizeof(test<Derived>(nullptr)) == sizeof(Yes); \
}; \
\
template<typename T> \
struct has_member_##member \
: public std::integral_constant<bool, HasMember_##member<T>::RESULT> { \
}; \
\
template<typename Model> \
using has_##member = typename \
std::enable_if<has_member_##member<Model>::value \
&& std::is_constructible<Model>::value, bool>::type; \
\
template<typename Model> \
using no_##member = typename \
std::enable_if<!has_member_##member<Model>::value \
|| !std::is_constructible<Model>::value, bool>::type;
// Generate the above structure for the following list of methods:
GENERATE_HAS_MEMBER(simpleMethod)
GENERATE_HAS_MEMBER(cachedMethod)
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
HIERARCHY FRONT-END
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS FooImpl **************************************************************/
// Forward declaration
class FooImpl;
// Alias
using FooImplPtr = std::shared_ptr<FooImpl>;
/**
* @class FooImpl
* Interface for implementation of Foo front-end
*/
class FooImpl : public std::enable_shared_from_this<FooImpl> {
public:
// Virtual methods
virtual void method() = 0;
};
/* CLASS SimpleFooImpl ********************************************************/
// Forward declaration
template<typename T>
class SimpleFooImpl;
// Alias
template<typename T>
using SimpleFooImplPtr = std::shared_ptr<SimpleFooImpl<T>>;
/**
* @class SimpleFooImpl
* Simple implementation of Foo front-end
*/
template<typename T>
class SimpleFooImpl
: public std::conditional<!std::is_void<typename T::base>::value,
SimpleFooImpl<typename T::base>, FooImpl>::type {
public:
// Alias
using TPtr = std::shared_ptr<T>;
// Constructor
SimpleFooImpl(TPtr t = TPtr())
: _t(std::move(t)) {
}
// Overriden methods
void method() override {
methodImpl();
}
private:
// Instance variables
TPtr _t;
// Concrete methods
template<typename U = T>
void methodImpl(no_simpleMethod<U>* dummy = nullptr) {
std::cout << "Doing nothing... (don't have method or not constructible)";
std::cout << std::endl;
}
template<typename U = T>
void methodImpl(has_simpleMethod<U>* dummy = nullptr) {
_t->simpleMethod(std::shared_ptr<SimpleFooImpl<U>>(make_shared()));
}
SimpleFooImplPtr<T> make_shared() {
return std::static_pointer_cast<SimpleFooImpl<T>>(this->shared_from_this());
}
};
/* CLASS CachedFooImpl ********************************************************/
// Forward declaration
template<typename T>
class CachedFooImpl;
// Alias
template<typename T>
using CachedFooImplPtr = std::shared_ptr<CachedFooImpl<T>>;
/**
* @class CachedFooImpl
* Cached implementation of Foo front-end
*/
template<typename T>
class CachedFooImpl
: public std::conditional<!std::is_void<typename T::base>::value,
CachedFooImpl<typename T::base>, FooImpl>::type {
public:
// Alias
using TPtr = std::shared_ptr<T>;
// Constructor
CachedFooImpl(TPtr t = TPtr())
: _t(std::move(t)) {
}
// Overriden methods
void method() override {
methodImpl();
}
// Concrete methods
int cache() {
return _cache;
}
private:
// Instance variables
TPtr _t;
int _cache = 0;
// Concrete methods
template<typename U = T>
void methodImpl(no_cachedMethod<U>* dummy = nullptr) {
std::cout << "Doing nothing... (don't have method or not constructible)";
std::cout << std::endl;
}
template<typename U = T>
void methodImpl(has_cachedMethod<U>* dummy = nullptr) {
_t->cachedMethod(std::shared_ptr<CachedFooImpl<U>>(make_shared()));
}
CachedFooImplPtr<T> make_shared() {
return std::static_pointer_cast<CachedFooImpl<T>>(this->shared_from_this());
}
};
/* CLASS Foo ******************************************************************/
// Forward declaration
class Foo;
// Alias
using FooPtr = std::shared_ptr<Foo>;
/**
* @class Foo
* Main class for Foo front-end
*/
class Foo {
public:
Foo(FooImplPtr impl) : _impl(std::move(impl)) {}
virtual void method() {
_impl->method();
}
private:
FooImplPtr _impl;
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
HIERARCHY BACK-END
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS Top ******************************************************************/
// Forward declaration
class Top;
// Alias
using TopPtr = std::shared_ptr<Top>;
/**
* @class Top
* Top of the class hierarchy
*/
class Top : public std::enable_shared_from_this<Top> {
public:
using base = void;
};
/* CLASS Baz ******************************************************************/
// Forward declaration
class Baz;
// Alias
using BazPtr = std::shared_ptr<Baz>;
/**
* @class Baz
* Basic son of Top
*/
class Baz : public Top {
public:
using base = Top;
};
/* CLASS Bar ******************************************************************/
// Forward declaration
class Bar;
// Alias
using BarPtr = std::shared_ptr<Bar>;
/**
* @class Bar
* Complex son of Top, with new frontend
*/
class Bar : public Top {
public:
using base = Top;
// Overriding methods
virtual FooPtr simpleFoo() = 0;
virtual FooPtr cachedFoo() = 0;
protected:
// Virtual methods
virtual void simpleMethodImpl(SimpleFooImpl<Bar> *simpleFoo) {
std::cout << "Running simple in Bar" << std::endl;
}
virtual void cachedMethodImpl(CachedFooImpl<Bar> *cachedFoo) {
std::cout << "Running cached in Bar" << std::endl;
cachedFoo->cache();
}
};
/* CLASS BarDerived ***********************************************************/
// Forward declaration
class BarDerived;
// Alias
using BarDerivedPtr = std::shared_ptr<BarDerived>;
/**
* @class BarDerived
* Class "overriding" parent implementation
*/
class BarDerived : public Bar {
public:
using base = Bar;
// Overriden methods
FooPtr simpleFoo() override {
return std::make_shared<Foo>(
std::make_shared<SimpleFooImpl<BarDerived>>(make_shared()));
}
FooPtr cachedFoo() override {
return std::make_shared<Foo>(
std::make_shared<CachedFooImpl<BarDerived>>(make_shared()));
}
// Concrete methods
template<typename... Params> // Required to implement `simpleFoo()`
void simpleMethod(SimpleFooImplPtr<BarDerived> simpleFoo, Params... params) {
std::cout << "BarDerived template method" << std::endl;
simpleMethodImpl(simpleFoo.get(), std::forward<Params>(params)...);
}
template<typename... Params> // Required to implement `cachedFoo()`
void cachedMethod(CachedFooImplPtr<BarDerived> cachedFoo, Params... params) {
std::cout << "BarDerived template method" << std::endl;
cachedMethodImpl(cachedFoo.get(), std::forward<Params>(params)...);
}
protected:
// Virtual methods
virtual void simpleMethodImpl(SimpleFooImpl<BarDerived> *simpleFoo) {
std::cout << "Running simple in BarDerived" << std::endl;
}
virtual void cachedMethodImpl(CachedFooImpl<BarDerived> *cachedFoo) {
std::cout << "Running cached in BarDerived" << std::endl;
cachedFoo->cache();
}
private:
BarDerivedPtr make_shared() {
return std::static_pointer_cast<BarDerived>(this->shared_from_this());
}
};
/* CLASS BarReusing ***********************************************************/
// Forward declaration
class BarReusing;
// Alias
using BarReusingPtr = std::shared_ptr<BarReusing>;
/**
* @class BarReusing
* Class reusing parent implementation
*/
class BarReusing : public Bar {
public:
using base = Bar;
// Overriden methods
FooPtr simpleFoo() override {
return std::make_shared<Foo>(
std::make_shared<SimpleFooImpl<BarReusing>>(make_shared()));
}
FooPtr cachedFoo() override {
return std::make_shared<Foo>(
std::make_shared<CachedFooImpl<BarReusing>>(make_shared()));
}
// Concrete methods
template<typename... Params> // Required to implement `simpleFoo()`
void simpleMethod(SimpleFooImplPtr<BarReusing> simpleFoo, Params... params) {
std::cout << "BarReusing template method" << std::endl;
simpleMethodImpl(simpleFoo.get(), std::forward<Params>(params)...);
}
template<typename... Params> // Required to implement `simpleFoo()`
void cachedMethod(CachedFooImplPtr<BarReusing> cachedFoo, Params... params) {
std::cout << "BarReusing template method" << std::endl;
cachedMethodImpl(cachedFoo.get(), std::forward<Params>(params)...);
}
private:
BarReusingPtr make_shared() {
return std::static_pointer_cast<BarReusing>(this->shared_from_this());
}
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
MAIN
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* FUNCTION main **************************************************************/
int main(int argc, char **argv) {
std::cout << std::endl;
std::cout << "Test BarDerived" << std::endl;
std::cout << "================" << std::endl;
auto barDerived = std::make_shared<BarDerived>();
barDerived->simpleFoo()->method();
barDerived->cachedFoo()->method();
std::cout << std::endl;
std::cout << "Test BarDerived casted to Bar" << std::endl;
std::cout << "==============================" << std::endl;
static_cast<BarPtr>(barDerived)->simpleFoo()->method();
static_cast<BarPtr>(barDerived)->cachedFoo()->method();
std::cout << std::endl;
std::cout << "Test BarReusing" << std::endl;
std::cout << "================" << std::endl;
auto barReusing = std::make_shared<BarReusing>();
barReusing->simpleFoo()->method();
barReusing->cachedFoo()->method();
std::cout << std::endl;
std::cout << "Test BarReusing casted to Bar" << std::endl;
std::cout << "==============================" << std::endl;
static_cast<BarPtr>(barReusing)->simpleFoo()->method();
static_cast<BarPtr>(barReusing)->cachedFoo()->method();
std::cout << std::endl;
return 0;
}
Man, it awesome! You are the best c++ programmer I've ever known. hahahahah
Hahaha, thanks! But let me tell you: I found what I believe to be an even better solution - with much less strange variadic templates. Take a look:
// Standard headers
#include <memory>
#include <iostream>
#include <typeinfo>
#include <type_traits>
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
IMPLEMENTATION HELPER
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* MACRO GENERATE_HAS_MEMBER **************************************************/
// This macro is aimed to create a member detector: a class and type trait
// that can be used to check when an attribute/method exists in a class.
// As there is an infinite number of names, it's impossible to create a
// standard type trait only with the resources the language provides. Given
// that, it's necessary to create a macro to automatically generate all
// classes and alias from a name given as parameter.
// The following macro creates:
// - A template class that uses SFINAE and multiple inheritance to decide if
// the member exists in the class. In case it does, the inner class `Derived`
// has two `member`s and its size is the same is a char[2]. This information
// is to store in `RESULT` a boolean that indicates a member exists.
// - A struct inheriting from `std::integral_constant`, which have a trait
// compliant with STL.
// - Two alias `has_##member` and `no_##member` to selectively create
// methods by applying SFINAE on its parameters.
#define GENERATE_HAS_MEMBER(member) \
\
template <typename T> \
class HasMember_##member \
{ \
private: \
using Yes = char[2]; \
using No = char[1]; \
\
struct Fallback { int member; }; \
struct Derived : T, Fallback { }; \
\
template<typename U> static No& test (decltype(U::member)*); \
template<typename U> static Yes& test (U*); \
\
public: \
static constexpr bool RESULT \
= sizeof(test<Derived>(nullptr)) == sizeof(Yes); \
}; \
\
template<typename T> \
struct has_member_##member \
: public std::integral_constant<bool, HasMember_##member<T>::RESULT> { \
}; \
\
template<typename Model> \
using has_##member = typename \
std::enable_if<has_member_##member<Model>::value \
&& std::is_constructible<Model>::value, bool>::type; \
\
template<typename Model> \
using no_##member = typename \
std::enable_if<!has_member_##member<Model>::value \
|| !std::is_constructible<Model>::value, bool>::type;
// Generate the above structure for the following list of methods:
GENERATE_HAS_MEMBER(simpleMethod)
GENERATE_HAS_MEMBER(cachedMethod)
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
HIERARCHY FRONT-END
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS FooImpl **************************************************************/
// Forward declaration
class FooImpl;
// Alias
using FooImplPtr = std::shared_ptr<FooImpl>;
/**
* @class FooImpl
* Interface for implementation of Foo front-end
*/
class FooImpl : public std::enable_shared_from_this<FooImpl> {
public:
// Virtual methods
virtual void method() = 0;
};
/* CLASS SimpleFooImpl ********************************************************/
// Forward declaration
template<typename T>
class SimpleFooImpl;
// Alias
template<typename T>
using SimpleFooImplPtr = std::shared_ptr<SimpleFooImpl<T>>;
/**
* @class SimpleFooImpl
* Simple implementation of Foo front-end
*/
template<typename T>
class SimpleFooImpl
: public std::conditional<!std::is_void<typename T::base>::value,
SimpleFooImpl<typename T::base>, FooImpl>::type {
public:
// Alias
using TPtr = std::shared_ptr<T>;
// Constructor
SimpleFooImpl(TPtr t = TPtr())
: _t(std::move(t)) {
}
// Overriden methods
void method() override {
methodImpl();
}
private:
// Instance variables
TPtr _t;
// Concrete methods
template<typename U = T>
void methodImpl(no_simpleMethod<U>* dummy = nullptr) {
std::cout << "Doing nothing... (don't have method or not constructible)";
std::cout << std::endl;
}
template<typename U = T>
void methodImpl(has_simpleMethod<U>* dummy = nullptr) {
_t->simpleMethod(std::shared_ptr<SimpleFooImpl<U>>(make_shared()));
}
SimpleFooImplPtr<T> make_shared() {
return std::static_pointer_cast<SimpleFooImpl<T>>(this->shared_from_this());
}
};
/* CLASS CachedFooImpl ********************************************************/
// Forward declaration
template<typename T>
class CachedFooImpl;
// Alias
template<typename T>
using CachedFooImplPtr = std::shared_ptr<CachedFooImpl<T>>;
/**
* @class CachedFooImpl
* Cached implementation of Foo front-end
*/
template<typename T>
class CachedFooImpl
: public std::conditional<!std::is_void<typename T::base>::value,
CachedFooImpl<typename T::base>, FooImpl>::type {
public:
// Alias
using TPtr = std::shared_ptr<T>;
using Cache = typename T::Cache;
// Constructor
CachedFooImpl(TPtr t = TPtr(), Cache cache = Cache())
: _t(std::move(t)), _cache(std::move(cache)) {
}
// Overriden methods
void method() override {
methodImpl();
}
// Concrete methods
Cache cache() {
return _cache;
}
private:
// Instance variables
TPtr _t;
Cache _cache;
// Concrete methods
template<typename U = T>
void methodImpl(no_cachedMethod<U>* dummy = nullptr) {
std::cout << "Doing nothing... (don't have method or not constructible)";
std::cout << std::endl;
}
template<typename U = T>
void methodImpl(has_cachedMethod<U>* dummy = nullptr) {
_t->cachedMethod(std::shared_ptr<CachedFooImpl<U>>(make_shared()));
}
CachedFooImplPtr<T> make_shared() {
return std::static_pointer_cast<CachedFooImpl<T>>(this->shared_from_this());
}
};
/* CLASS Foo ******************************************************************/
// Forward declaration
class Foo;
// Alias
using FooPtr = std::shared_ptr<Foo>;
/**
* @class Foo
* Main class for Foo front-end
*/
class Foo {
public:
Foo(FooImplPtr impl) : _impl(std::move(impl)) {}
virtual void method() {
_impl->method();
}
private:
FooImplPtr _impl;
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
HIERARCHY BACK-END
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS Top ******************************************************************/
// Forward declaration
class Top;
// Alias
using TopPtr = std::shared_ptr<Top>;
/**
* @class Top
* Top of the class hierarchy
*/
class Top : public std::enable_shared_from_this<Top> {
public:
using base = void;
using Cache = int;
};
/* CLASS Baz ******************************************************************/
// Forward declaration
class Baz;
// Alias
using BazPtr = std::shared_ptr<Baz>;
/**
* @class Baz
* Basic son of Top
*/
class Baz : public Top {
public:
using base = Top;
};
/* CLASS Bar ******************************************************************/
// Forward declaration
class Bar;
// Alias
using BarPtr = std::shared_ptr<Bar>;
/**
* @class Bar
* Complex son of Top, with definition of new front-end
*/
class Bar : public Top {
public:
using base = Top;
// Purely virtual methods
virtual FooPtr simpleFoo() = 0;
virtual FooPtr cachedFoo() = 0;
// Virtual methods
virtual void simpleMethod(SimpleFooImplPtr<Bar> simpleFoo) {
std::cout << "Running simple in Bar" << std::endl;
}
virtual void cachedMethod(CachedFooImplPtr<Bar> cachedFoo) {
std::cout << "Running cached in Bar" << std::endl;
std::cout << "Cache: " << typeid(cachedFoo->cache()).name() << std::endl;
}
};
/* CLASS BarCrtp **************************************************************/
// Forward declaration
template<typename Derived>
class BarCrtp;
// Alias
template<typename Derived>
using BarCrtpPtr = std::shared_ptr<BarCrtp<Derived>>;
/**
* @class BarCrtp
* Implementation of front-end, using CRTP to inject methods in subclasses
*/
template<typename Derived>
class BarCrtp : public Bar {
public:
using base = Bar;
using DerivedPtr = std::shared_ptr<Derived>;
// Overriding methods
FooPtr simpleFoo() override {
return std::make_shared<Foo>(
std::make_shared<SimpleFooImpl<Derived>>(make_shared()));
}
FooPtr cachedFoo() override {
return std::make_shared<Foo>(
std::make_shared<CachedFooImpl<Derived>>(make_shared()));
}
private:
DerivedPtr make_shared() {
return std::static_pointer_cast<Derived>(
static_cast<Derived *>(this)->shared_from_this());
}
};
/* CLASS BarDerived ***********************************************************/
// Forward declaration
class BarDerived;
// Alias
using BarDerivedPtr = std::shared_ptr<BarDerived>;
/**
* @class BarDerived
* Class "overriding" parent implementation
*/
class BarDerived : public BarCrtp<BarDerived> {
public:
using base = Bar;
using Cache = double;
virtual void simpleMethod(SimpleFooImplPtr<BarDerived> simpleFoo) {
std::cout << "Running simple in BarDerived" << std::endl;
}
virtual void cachedMethod(CachedFooImplPtr<BarDerived> cachedFoo) {
std::cout << "Running cached in BarDerived" << std::endl;
std::cout << "Cache: " << typeid(cachedFoo->cache()).name() << std::endl;
}
};
/* CLASS BarReusing ***********************************************************/
// Forward declaration
class BarReusing;
// Alias
using BarReusingPtr = std::shared_ptr<BarReusing>;
/**
* @class BarReusing
* Class reusing parent implementation
*/
class BarReusing : public BarCrtp<BarReusing> {
public:
using base = Bar;
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
MAIN
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* FUNCTION main **************************************************************/
int main(int argc, char **argv) {
std::cout << std::endl;
std::cout << "Test BarDerived" << std::endl;
std::cout << "================" << std::endl;
auto barDerived = std::make_shared<BarDerived>();
barDerived->simpleFoo()->method();
barDerived->cachedFoo()->method();
std::cout << std::endl;
std::cout << "Test BarDerived casted to Bar" << std::endl;
std::cout << "==============================" << std::endl;
static_cast<BarPtr>(barDerived)->simpleFoo()->method();
static_cast<BarPtr>(barDerived)->cachedFoo()->method();
std::cout << std::endl;
std::cout << "Test BarReusing" << std::endl;
std::cout << "================" << std::endl;
auto barReusing = std::make_shared<BarReusing>();
barReusing->simpleFoo()->method();
barReusing->cachedFoo()->method();
std::cout << std::endl;
std::cout << "Test BarReusing casted to Bar" << std::endl;
std::cout << "==============================" << std::endl;
static_cast<BarPtr>(barReusing)->simpleFoo()->method();
static_cast<BarPtr>(barReusing)->cachedFoo()->method();
std::cout << std::endl;
return 0;
}
I changed two things:
- In the previous version, we had a public concrete template method and a protected virtual method - the first receiving a shared pointer and the second, a raw pointer. After running some tests, I realized there was no advantage in having this. I though the polimorphism was happening because of this virtual methods. But, in fact, the instanciation of
SimpleFooImpl<T>
/CachedFooImpl<T>
insimpleFoo
/cahcedFoo
in the subclasses was the responsible for that. So, instead of implementing two methods for every function, someone creating a new probabilistic model will have to override only one methods of each kind. - Furthermore, every subclass of
Bar
needs to implement methodssimpleFoo
andcachedFoo
. To simplify that, we can use the Curiously Recurring Template Pattern (CRTP). Basically, we split the classBar
in two:Bar
, with purely virtual methods forsimpleFoo
andcachedFoo
and the subclassBarCrtp<T>
, which implements them. The advantage is:T
represents the subclass ofBarCrtp<T>
, so when creatingBarDerived
andBarReusing
,BarCrtp<T>
can do compile time polimorphism and "inject"simpleFoo
andcachedFoo
. In short: instead of replicating this methods around, we can centralize them in just one class.
Well, I think this one will be my final implementation for the reformulated architecture.
Here, method
in Foo
could be applied for two different types: Target
and Spot
. Check the implementation:
// Standard headers
#include <memory>
#include <iostream>
#include <typeinfo>
#include <exception>
#include <type_traits>
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
IMPLEMENTATION HELPER
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
#define GENERATE_HAS_MEMBER(member) \
\
template <typename T> \
class HasMember_##member \
{ \
private: \
using Yes = char[2]; \
using No = char[1]; \
\
struct Fallback { int member; }; \
struct Derived : T, Fallback { }; \
\
template<typename U> static No& test (decltype(U::member)*); \
template<typename U> static Yes& test (U*); \
\
public: \
static constexpr bool RESULT \
= sizeof(test<Derived>(nullptr)) == sizeof(Yes); \
}; \
\
struct no_##member##_tag {}; \
struct has_##member##_tag {}; \
\
template<typename T> \
struct has_member_##member \
: public std::integral_constant<bool, HasMember_##member<T>::RESULT> { \
\
using tag = typename std::conditional<has_member_##member<T>::value, \
has_##member##_tag, no_##member##_tag>::type; \
};
// Generate the above structure for the following list of methods:
GENERATE_HAS_MEMBER(simpleMethod)
GENERATE_HAS_MEMBER(cachedMethod)
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
COMMON CLASSES
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS Target ***************************************************************/
class Target {
};
/* CLASS Spot *****************************************************************/
class Spot {
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
HIERARCHY FRONT-END
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS Foo ******************************************************************/
// Forward declaration
template<typename T>
class Foo;
// Alias
template<typename T>
using FooPtr = std::shared_ptr<Foo<T>>;
/**
* @class Foo
* Interface for implementation of Foo front-end
*/
template<typename T>
class Foo : public std::enable_shared_from_this<Foo<T>> {
public:
// Virtual methods
virtual void method() = 0;
};
/* CLASS SimpleFoo ************************************************************/
// Forward declaration
template<typename T, typename M>
class SimpleFoo;
// Alias
template<typename T, typename M>
using SimpleFooPtr = std::shared_ptr<SimpleFoo<T, M>>;
/**
* @class SimpleFoo
* Simple implementation of Foo front-end
*/
template<typename T, typename M>
class SimpleFoo
: public std::conditional<!std::is_void<typename M::base>::value,
SimpleFoo<T, typename M::base>, Foo<T>>::type {
public:
// Alias
using MPtr = std::shared_ptr<M>;
// Constructor
SimpleFoo(MPtr m = MPtr())
: _m(std::move(m)) {
}
public:
// Overriden methods
void method() override {
methodImpl(typename has_member_simpleMethod<M>::tag());
}
private:
// Instance variables
MPtr _m;
// Concrete methods
void methodImpl(no_simpleMethod_tag) {
throw std::logic_error("Class don't have method!");
}
void methodImpl(has_simpleMethod_tag) {
_m->simpleMethod(make_shared());
}
SimpleFooPtr<T, M> make_shared() {
return std::static_pointer_cast<SimpleFoo<T, M>>(
this->shared_from_this());
}
};
/* CLASS CachedFoo ************************************************************/
// Forward declaration
template<typename T, typename M>
class CachedFoo;
// Alias
template<typename T, typename M>
using CachedFooPtr = std::shared_ptr<CachedFoo<T, M>>;
/**
* @class CachedFoo
* Cached implementation of Foo front-end
*/
template<typename T, typename M>
class CachedFoo
: public std::conditional<!std::is_void<typename M::base>::value,
CachedFoo<T, typename M::base>, Foo<T>>::type {
public:
// Alias
using MPtr = std::shared_ptr<M>;
using Cache = typename M::Cache;
// Constructor
CachedFoo(MPtr m = MPtr(), Cache cache = Cache())
: _m(std::move(m)), _cache(std::move(cache)) {
}
// Overriden methods
void method() override {
methodImpl(typename has_member_cachedMethod<M>::tag());
}
// Concrete methods
Cache cache() {
return _cache;
}
private:
// Instance variables
MPtr _m;
Cache _cache;
// Concrete methods
void methodImpl(no_cachedMethod_tag) {
throw std::logic_error("Class don't have method!");
}
void methodImpl(has_cachedMethod_tag) {
_m->cachedMethod(make_shared());
}
CachedFooPtr<T, M> make_shared() {
return std::static_pointer_cast<CachedFoo<T, M>>(
this->shared_from_this());
}
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
HIERARCHY BACK-END
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* CLASS Top ******************************************************************/
// Forward declaration
class Top;
// Alias
using TopPtr = std::shared_ptr<Top>;
/**
* @class Top
* Top of the class hierarchy
*/
class Top : public std::enable_shared_from_this<Top> {
public:
using base = void;
using Cache = int;
};
/* CLASS Baz ******************************************************************/
// Forward declaration
class Baz;
// Alias
using BazPtr = std::shared_ptr<Baz>;
/**
* @class Baz
* Basic son of Top
*/
class Baz : public Top {
public:
using base = Top;
};
/* CLASS Bar ******************************************************************/
// Forward declaration
class Bar;
// Alias
using BarPtr = std::shared_ptr<Bar>;
/**
* @class Bar
* Complex son of Top, with definition of new front-end
*/
class Bar : public Top {
public:
using base = Top;
// Purely virtual methods
virtual FooPtr<Target> targetFoo(bool cached) = 0;
virtual FooPtr<Spot> spotFoo(bool cached) = 0;
};
/* CLASS BarCrtp **************************************************************/
// Forward declaration
template<typename Derived>
class BarCrtp;
// Alias
template<typename Derived>
using BarCrtpPtr = std::shared_ptr<BarCrtp<Derived>>;
/**
* @class BarCrtp
* Implementation of front-end, using CRTP to inject methods in subclasses
*/
template<typename Derived>
class BarCrtp : public Bar {
public:
using base = Bar;
using DerivedPtr = std::shared_ptr<Derived>;
// Overriding methods
FooPtr<Target> targetFoo(bool cached = true) override {
if (cached)
return std::make_shared<CachedFoo<Target, Derived>>(make_shared());
return std::make_shared<SimpleFoo<Target, Derived>>(make_shared());
}
FooPtr<Spot> spotFoo(bool cached = true) override {
if (cached)
return std::make_shared<CachedFoo<Spot, Derived>>(make_shared());
return std::make_shared<SimpleFoo<Spot, Derived>>(make_shared());
}
// Virtual methods
virtual void simpleMethod(SimpleFooPtr<Target, Derived> simpleFoo) {
std::cout << "Running simple for Target in BarCrtp" << std::endl;
}
virtual void cachedMethod(CachedFooPtr<Target, Derived> cachedFoo) {
std::cout << "Running cached for Target in BarCrtp" << std::endl;
std::cout << "Cache: " << typeid(cachedFoo->cache()).name() << std::endl;
}
virtual void simpleMethod(SimpleFooPtr<Spot, Derived> simpleFoo) {
std::cout << "Running simple for Spot in BarCrtp" << std::endl;
}
virtual void cachedMethod(CachedFooPtr<Spot, Derived> cachedFoo) {
std::cout << "Running cached for Spot in BarCrtp" << std::endl;
std::cout << "Cache: " << typeid(cachedFoo->cache()).name() << std::endl;
}
private:
DerivedPtr make_shared() {
return std::static_pointer_cast<Derived>(
static_cast<Derived *>(this)->shared_from_this());
}
};
/* CLASS BarDerived ***********************************************************/
// Forward declaration
class BarDerived;
// Alias
using BarDerivedPtr = std::shared_ptr<BarDerived>;
/**
* @class BarDerived
* Class "overriding" parent implementation
*/
class BarDerived : public BarCrtp<BarDerived> {
public:
using base = Bar;
using Cache = double;
// Overriden methods
void simpleMethod(SimpleFooPtr<Target, BarDerived> simpleFoo) override {
std::cout << "Running simple for Target in BarDerived" << std::endl;
}
void cachedMethod(CachedFooPtr<Target, BarDerived> cachedFoo) override {
std::cout << "Running cached for Target in BarDerived" << std::endl;
std::cout << "Cache: " << typeid(cachedFoo->cache()).name() << std::endl;
}
void simpleMethod(SimpleFooPtr<Spot, BarDerived> simpleFoo) override {
std::cout << "Running simple for Spot in BarDerived" << std::endl;
}
void cachedMethod(CachedFooPtr<Spot, BarDerived> cachedFoo) override {
std::cout << "Running cached for Spot in BarDerived" << std::endl;
std::cout << "Cache: " << typeid(cachedFoo->cache()).name() << std::endl;
}
};
/* CLASS BarReusing ***********************************************************/
// Forward declaration
class BarReusing;
// Alias
using BarReusingPtr = std::shared_ptr<BarReusing>;
/**
* @class BarReusing
* Class reusing parent implementation
*/
class BarReusing : public BarCrtp<BarReusing> {
public:
using base = Bar;
};
/*
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
-------------------------------------------------------------------------------
MAIN
-------------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////////////
*/
/* FUNCTION main **************************************************************/
int main(int argc, char **argv) {
std::cout << std::endl;
std::cout << "Test BarDerived" << std::endl;
std::cout << "================" << std::endl;
auto barDerived = std::make_shared<BarDerived>();
barDerived->targetFoo(false)->method();
barDerived->targetFoo(true)->method();
barDerived->spotFoo(false)->method();
barDerived->spotFoo(true)->method();
std::cout << std::endl;
std::cout << "Test BarDerived casted to Bar" << std::endl;
std::cout << "==============================" << std::endl;
static_cast<BarPtr>(barDerived)->targetFoo(false)->method();
static_cast<BarPtr>(barDerived)->targetFoo(true)->method();
static_cast<BarPtr>(barDerived)->spotFoo(false)->method();
static_cast<BarPtr>(barDerived)->spotFoo(true)->method();
std::cout << std::endl;
std::cout << "Test BarReusing" << std::endl;
std::cout << "================" << std::endl;
auto barReusing = std::make_shared<BarReusing>();
barReusing->targetFoo(false)->method();
barReusing->targetFoo(true)->method();
barReusing->spotFoo(false)->method();
barReusing->spotFoo(true)->method();
std::cout << std::endl;
std::cout << "Test BarReusing casted to Bar" << std::endl;
std::cout << "==============================" << std::endl;
static_cast<BarPtr>(barReusing)->targetFoo(false)->method();
static_cast<BarPtr>(barReusing)->targetFoo(true)->method();
static_cast<BarPtr>(barReusing)->spotFoo(false)->method();
static_cast<BarPtr>(barReusing)->spotFoo(true)->method();
std::cout << std::endl;
return 0;
}
So far, this is the most simple (and I think the best) version:
- After making some changes, I realized there is no need to a Bridge Pattern in our front-end classes:
Foo
is an interface (aka purely abstract class), which has two subclassesSimpleFoo
andCachedFoo
. Foo
becomes a template class with parameterT
(forTarget
orSpot
), andSimpleFoo
/CachedFoo
receive this extra parameter (besidesM
, which is a class in the main hierarchy). This extra parameter makesSimpleFoo<Target, M>
andSimpleFoo<Spot, M>
be different - and polymorphism is enough to implement methods with the same name inBar
andBarDerived
.- Superclass implementations go to
BarCrtp<Derived>
instead ofBar
. This allows its methods to have the same signature they would have in the subclasses (acting as this methods had a "default implementation" that is injected in the subclasses). Finally, a subclass asBarDerived
can only override this default methods, as someone would naturally do when she wants to change a method in a subclass.
New TODO list to change Generator's parameter from target to decorator:
- Change
Generator
/SimpleGenerator
parameters fromTarget
toDecorator<Target>
. - Rename method
choose
tochooseSequence
inGenerator
. - Rename method
simpleChoose
tosimpleChooseSequence
in main hierarchy. - Rename method
choose
tosimpleChooseSymbol
in main hierarchy. - Rename method
chooseSequencesPosition
tosimpleChooseSymbol
. - Move standard
simpleChooseSymbol
fromProbabilisticModel
toProbabilisticModelCrtp
. - Move labeling
simpleChooseSymbol
fromDecodableModel
toDecodableModelCrtp
. - Add method
chooseSymbol
inGenerator
/SimpleGenerator
.
We need to include a number generator to the generator frontend