I am writing a C ++ library for optimization, and I ran into a curious problem with contravariant types.
So, I define a hierarchy of "functions" based on the information that they can calculate.
class Function { public: double value()=0; } class DifferentiableFunction : public Function { public: const double* gradient()=0; } class TwiceDifferentiableFunction : public DifferentiableFunction { public: const double* hessian()=0; }
That all is well and good, but now I want to define interfaces for optimizers. For example, some optimizers require gradient or hessian information for optimization, and some do not. Thus, optimizer types are contravariant to function types.
class HessianOptimizer { public: set_function(TwiceDifferentiableFunction* f)=0; } class GradientOptimizer : public HessianOptimizer { public: set_function(DifferentiableFunction* f)=0; } class Optimizer: public GradientOptimizer { public: set_function(TwiceDifferentiableFunction* f)=0; }
I believe this makes sense in terms of type theory, but the strange thing is that usually when people want to extend the code, they inherit existing classes. For example, if someone else used this library, and they wanted to create a new type of optimizer that requires more information than hessian, they can create a class, for example
class ThriceDifferentiableFunction: public TwiceDifferentiableFunction } public: const double* thirdderivative()=0; }
But then, to create the appropriate optimizer class, we would need to make HessianOptimizer an extension of ThirdOrderOptimizer. But the library user will have to change the library to do this! Therefore, although we can add to the ThriceDifferentiableFunction without having to change the library, it seems that contravariant types lose this property. This seems to be just an artifact that classes declare parent types, not types of their children.
But how should you handle this? Is there any way to make it beautiful?