We are going to be writing a _lot_ of code that is templatized by "scalar type" so that we can instantiate the code for computation (scalar type==double), differentiation (scalar type==Eigen::AutoDiffScalar<DerType>), and possibly other types for analysis. Using ScalarType as the template argument leads to code that is repetitive and hard to parse (for humans), like:
template <typename ScalarType> class MyClass {
static Eigen::Matrix<ScalarType, Eigen::Dynamic, 1>
AddVectors(const Eigen::Matrix<ScalarType, Eigen::Dynamic, 1>& left,
const Eigen::Matrix<ScalarType, Eigen::Dynamic, 1>& right);
};
Good programmers won't allow such repetition to obscure their code, so people are going to abbreviate in some way. It would be very helpful if we standardize those abbreviations in Drake. I would like to propose some abbreviations and then haggle over them here. These are mostly adapted from Eigen's abbreviation collection and stolen from @amcastro-tri and @david-german-tri's PRs.
I propose that we standardize on the maximally-terse abbreviation S for ScalarType. David prefers Scalar. Assume S here for a moment. Then, following Eigen's abbr. style (e.g. VectorXf for dynamic-sized column matrix of floats) I propose:
// Defined somewhere in drake/common:
template <typename S> using VectorX = Eigen::Matrix<S, Eigen::Dynamic, 1>;
template <typename S> using MatrixX = Eigen::Matrix<S, Eigen::Dynamic, Eigen::Dynamic>;
With those abbreviations and S for scalar type, we can use VectorX<S> directly, reducing the above code to:
template <typename S> class MyClass {
static VectorX<S> AddVectors(const VectorX<S>& left, const VectorX<S>& right);
};
If we use a longer name for the scalar type, there will likely be local abbreviations in templatized classes and methods. If we go that way, we should standardize those too, for example:
template <typename Scalar> class MyClass {
using VectorXs = VectorX<Scalar>;
static VectorXs AddVectors(const VectorXs& left, const VectorXs& right);
};
My arguments for why to use S rather than Scalar or ScalarType:
T: hides the unimportant and repetitive parameter and exposes the meaningful code; easy for humans to pattern-match into invisibility; reduces need to invent other abbreviations for templatized subobjects.Scalar is deceptively like Eigen's _Scalar argument. But any Eigen-suitable scalar must provide a typedef Scalar, for example typename AutoDiffScalar<DerType>::Scalar. That means if we use Scalar we will be writing a lot of typename Scalar::Scalar. I would rather see typename S::Scalar.Please comment, argue, counterpropose, and suggest other related abbreviations if I've forgotten some important ones.
/cc @tkoolen
What would be the argument against using T for the type parameter in our code?
template <typename T> using VectorX = Eigen::Matrix<T, Eigen::Dynamic, 1>;
template <typename T> using MatrixX = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
template <typename T>
class MyClass {
static VectorX<T> AddVectors(const VectorX<T>& left, const VectorX<T>& right);
};
Perhaps that would satisfy everyone?
Here's an argument against using plain-old T:
T does not convey any scope on what types it may be. Users will be left guessing which type values T could take on. This is as opposed to ScalarType, which signifies that the type must be some kind of scalar value.
I vote for spelling things out completely for clarity:
template <typename ScalarType> class MyClass {
using VectorXs = VectorX<ScalarType>;
static VectorXs AddVectors(const VectorXs& left, const VectorXs& right);
};
I believe the standard abbreviations will effectively alleviate the clutter. The only downside is the need to scatter the abbreviations everywhere.
I agree with @liangfok: T is universally known but implies that any type is acceptable, which isn't true here. S doesn't convey much of anything to an unfamiliar reader. One-letter variable names are a longstanding pain point in Drake; one-letter template names have the same problem.
I personally down vote the choice of Scalar, ScalarType or any other naming that would imply the template parameter is some kind of scalar. The template parameter can also be something as complex as a polynomial and maybe other objects that we still don't even foresee (like chaotic polynomials for instance). Also @sherm1's point on Eigen having a typename S::Scalar would make things even more confusing regarding to what actually the scalar is (mathematically speaking I would expect the scalar to be a real or complex number but not a polynomial).
I think S would be a good choice once all agree on it and avoid typing long lines of code. Consider the hierarchy of types for instance for a Polynomial<complex<double>>. Within a given class we would have code like so:
// Take for instance S = Polynomial<complex<double>>
template <class S>
class MyClass {
typedef typename S::Scalar Scalar; // would reduce to complex<double>
typedef typename NumTraits<Scalar>::Real Real; // would reduce to double
};
Notice that NumTraits<T> is defined by Eigen. Eigen objects all have the ::Scalar typedef member defined (including AutoDiffScalar).
Why we could ever like having something like a complex number in there? see @sherm1's comment in #1312 for complex step derivatives. And also we might not be foreseeing other uses of Drake that external might have.
Hmm, since _any_ type can indeed potentially be used, maybe we should use T. Why should we select S over T?
Yup. You guys discovered why I posited T :smile:.
I think <typename T> plus @tparam T must satisfy concept_foo_per_link_ in the doxygen class overview gets us exactly what we want: brevity in code, but clarity of intent.
T with a nicely-documented Concept for it to satisfy would be great. To the extent that we can capture that with static_asserts, even better.
I was trying to think in terms of what actually T could be in terms of a mathematical abstraction (T could not be a class Person for instance but a space where addition and multiplication, at least, are defined). Polynomials are case of rings while the real numbers are an Abelian group. Rings seem to be the most primitive space (multiplication does not have an inverse, like for polynomials). Neither of these start with either T or S :-(
So far, I've heard that T could potentially be:
doubleAutoDiffScalar<Eigen::Matrix<double,Eigen::Dynamic,1>>.Polynomial<complex<double>>Any others?
a TrigPoly and again I think there will be many others in the future. I would love to try to pass chaotic polynomials through my system myself (that link has application to V&V for vehicles!!)
I just realize I like T better. My reason is pretty simple. Regarding the snippet I showed before:
// Take for instance T = Polynomial<complex<double>>
template <class T>
class MyClass {
typedef typename T::Scalar Scalar; // would reduce to complex<double>
typedef typename Eigen::NumTraits<Scalar>::Real Real; // would reduce to double
// Now see what happens when I try to specialize some Eigen types:
typedef VectorX<T> VectorXt;
typedef VectorX<Scalar> VectorXs;
typedef VectorX<Real> VectorXr;
};
If I had S and Scalar then my two vector types, VectorXt and VectorXs would have the same names!. And since our type can be quite complex (essentially a field with the operations + and *, or Abelian group I believe it's called), I think it is ok to use a generic T.
+1 for T
I'm sold on T also.
You've talked me out of Scalar, and I'm willing to accede to the majority on T, though I wish something more descriptive had carried the day. (AbelianType?)
We agreed on T for Scalar type and have been using it throughout System 2/Simulator 2. Old code has not been converted but I think that's OK as long as we continue to use it in new code. So I'm closing this issue.
Most helpful comment
I'm sold on T also.