Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Contravariant types and extensibility

I'm writing a C++ library for optimization, and I've encountered a curious issue with contra-variant types.

So, I define a hierarchy of "functions", based on what information they can compute.

class Function {
 public:
    double value()=0;
}

class DifferentiableFunction : public Function {
 public:
  const double* gradient()=0;
}

class TwiceDifferentiableFunction : public DifferentiableFunction {
 public:
    const double* hessian()=0;
}

Which is all well and good, but now I want to define interfaces for the optimizers. For example, some optimizers require gradient information, or hessian information in order to optimize, and some don't. So the types of the optimizers are contravariant to the types of the functions.

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;
}

Which I suppose makes sense from a type theoretic perspective, but the thing that is weird about it is that usually when people want to extend code, they will inherit the already existing classes. So for example, if someone else was using this library, and they wanted to create a new type of optimizer that requires more information than the hessian, they might create a class like

class ThriceDifferentiableFunction: public TwiceDifferentiableFunction }
 public:
    const double* thirdderivative()=0;
}

But then to create the corresponding optimizer class, we would have to make HessianOptimizer extend ThirdOrderOptimizer. But the library user would have to modify the library to do so! So while we can add on the ThriceDifferentiableFunction without having to modify the library, it seems like the contravariant types lose this property. This seems to just be an artifact of the fact the classes declare their parent types rather than their children types.

But how are you supposed to deal with this? Is there any way to do it nicely?

like image 296
Jeremy Salwen Avatar asked Mar 16 '15 10:03

Jeremy Salwen


1 Answers

Since they're just interfaces, you don't have to be afraid of multiple inheritance with them. Why not make the optimiser types siblings instead of descendants?

class OptimizerBase
{
  // Common stuff goes here
};

class HessianOptimizer : virtual public OptimizerBase {
 public:
    virtual set_function(TwiceDifferentiableFunction* f)=0;
}

class GradientOptimizer : virtual public OptimizerBase {
 public:
    virtual set_function(DifferentiableFunction* f)=0;
}

class Optimizer : virtual public OptimizerBase {
 public:
    virtual set_function(TwiceDifferentiableFunction* f)=0;
}


// impl

class MyGradientOptimizer : virtual public GradientOptimizer, virtual public HessianOptimizer
{
  // ...
};
like image 91
Angew is no longer proud of SO Avatar answered Sep 28 '22 16:09

Angew is no longer proud of SO