How do I reduce repeated generic specification?

114 Views Asked by At

In this example, I repeat na::SVector<f64, NumStates> a bunch, can I define a type

type StateVector = na::SVector<f64, NumStates>;

like this? I get an error that associated type defaults are unstable. Is there another way?

use nalgebra as na;

pub trait Cost<const NumStates: usize, const NumControls: usize> {
    fn stage_cost(&self, x: &na::SVector<f64, NumStates>, u: &na::SVector<f64, NumControls>)
        -> f64;
    fn stage_grad(
        &self,
        x: &na::SVector<f64, NumStates>,
        u: &na::SVector<f64, NumControls>,
        grad_x: &mut na::SVector<f64, NumStates>,
        grad_u: &mut na::SVector<f64, NumControls>,
    );
    fn terminal_cost(&self, x: &na::SVector<f64, NumStates>) -> f64;
    fn terminal_grad(
        &self,
        x: &na::SVector<f64, NumStates>,
        grad: &mut na::SVector<f64, NumStates>,
    );
}
2

There are 2 best solutions below

0
On

As a commenter points out, position matters. I suspect your codeblock creating the error looks like this:

use nalgebra as na;

pub trait Cost<const NumStates: usize, const NumControls: usize> {
    type StateVector = na::SVector<f64, NumStates>;

    fn stage_cost(&self, x: &na::SVector<f64, NumStates>, u: &na::SVector<f64, NumControls>)
        -> f64;
    fn stage_grad(
        &self,
        x: &na::SVector<f64, NumStates>,
        u: &na::SVector<f64, NumControls>,
        grad_x: &mut na::SVector<f64, NumStates>,
        grad_u: &mut na::SVector<f64, NumControls>,
    );
    fn terminal_cost(&self, x: &na::SVector<f64, NumStates>) -> f64;
    fn terminal_grad(
        &self,
        x: &na::SVector<f64, NumStates>,
        grad: &mut na::SVector<f64, NumStates>,
    );
}

This creates an associated type which cannot have a default type on stable.

Define your type alias outside of the trait body and you've solved your problem:

use algebra as na;

type StateVector<const D: usize> = na::SVector<f64, D>;

pub trait Cost<const NumStates: usize, const NumControls: usize> {
    fn stage_cost(&self, x: &StateVector<NumStates>, u: &StateVector<NumControls>)
        -> f64;
    fn stage_grad(
        &self,
        x: &StateVector<NumStates>,
        u: &StateVector<NumControls>,
        grad_x: &mut StateVector<NumStates>,
        grad_u: &mut StateVector<NumControls>,
    );
    fn terminal_cost(&self, x: &StateVector<NumStates>) -> f64;
    fn terminal_grad(
        &self,
        x: &StateVector<NumStates>,
        grad: &mut StateVector<NumStates>,
    );
}
0
On

The error associated type defaults are unstable states that it is unstable to assign a default type to associated types on a trait, but you can still add associated traits without default values. So just... don't. Even if you add a default, it won't stop someone from not following your vector length requirements and changing the types upon implementing the trait.

After some tinkering here is what I came up with. I ended up shortening StateVector to States since the long name was almost as bad as the original. Plus who knows, maybe I want to try implementing your trait for my type using std::collections::LinkedList<f64> to hold my data.

pub trait Cost<const NUM_STATES: usize, const NUM_CONTROLS: usize> {
    type States;
    type Controls;

    fn stage_cost(&self, x: &Self::States, u: &Self::Controls) -> f64;
    fn stage_grad(
        &self,
        x: &Self::States,
        u: &Self::Controls,
        grad_x: &mut Self::States,
        grad_u: &mut Self::Controls,
    );
    fn terminal_cost(&self, x: &Self::States) -> f64;
    fn terminal_grad(&self, x: &Self::States, grad: &mut Self::States);
}

But then again, if we are changing the trait signature, why not skip the constants?

pub trait Cost<States, Controls> {
    fn stage_cost(&self, x: &States, u: &Controls) -> f64;
    fn stage_grad(&self, x: &States, u: &Controls, grad_x: &mut States, grad_u: &mut Controls);
    fn terminal_cost(&self, x: &States) -> f64;
    fn terminal_grad(&self, x: &States, grad: &mut States);
}

However, this does come at the cost of making usages of this trait longer.

// Example usage of origional
where A: Cost<S, C>,
// First suggestion
where B: Costs<S, C, States=SVector<f64, S>, Controls=SVector<f64, C>>,
// Second suggestion
where C: Costs<SVector<f64, S>, SVector<f64, C>>,

It may be best to leave your original as-is. That being said, one change I would make would be to import use nalgebra::SVector so you can remove the na:: prefixes for the vectors.