My understanding is that "normal" vectors (such as position and velocity) are said to be "contravariant" with respect to changes in coordinate systems because the coordinates of the vector transform in the opposite direction to the change of basis. Imagine holding a clock face in your hands, with the minute hand representing a vector pointing in a certain direction. Now turn the entire clock face 90 degrees clockwise. This represents a change in basis. In order to adjust the minute hand to point to its original position, it must be rotated counter-clockwise by 90 degrees – the change in vector coordinates contra-varies with the change in basis.

So now we want to express this idea in terms of functions and types. Following Brian’s idea that “coordinitization” is the functor in this problem:

public class Vector { }

public delegate double[] ToCoords<in TBasis>(Vector vector, TBasis basis);

Here Vector is the type for coordinate-free vectors, and ToCoords maps a vector to a set of vector coordinates given a vector basis (i.e. a coordinate system). This delegate type is contravariant in both the basis and vector arguments.

Now suppose we have a ToCoords<A> for a certain vector basis A. What happens if we apply a transformation from basis A to basis B? We want to compute ToCoords<B> in order to obtain vector coordinates for the new basis. Here’s how ToCoords changes with a change in basis:

public static ToCoords<B> TransformCoords<A, B>(this ToCoords<A> toCoordsA, Func<B, A> basisB2A)

{

return (vector, basis_b) => toCoordsA(vector, basisB2A(basis_b));

}

Notice that in order to go from a ToCoords<A> to a ToCoords<B> we need a function from basis B to basis A.  This is the inverse of the A -> B transformation we applied to start things off.

Now suppose we apply two transformations in a row, one from A to B and then one from B to C. In order to “back out” the change to the vector coordinates, we need to apply the inverse transformations in reverse order. So the following two expressions are equivalent:

ToCoords<C> tcc1 = tca.TransformCoords<A, C>(x => f(g(x)));

ToCoords<C> tcc2 = tca.TransformCoords(f).TransformCoords(g);

This is the opposite behaviour under composition to that of Enumerable.Select that Erik gave in the video, since Select is covariant in T and ToCoords is contravariant in TBasis.

Now there are some more “exotic” things in physics that are covariant with respect to a change in basis. They are called variously covectors, pseudovectors, dual vectors, row vectors, bivectors (my personal favourite), or even “linear functionals”. The last of these is probably the easiest to explain.

A linear functional is a function that takes a set of vector coordinates and returns a scalar (a number) through a process that looks a lot like a dot product of vectors. It’s easier to explain in code:

public delegate double LinearFunctional<out TBasis>(Vector vector, ToCoords<TBasis> toCoords);

public static LinearFunctional<TBasis> CreateLinearFunctional<TBasis>(TBasis basis, double[] coords)

{

return (vector, toCoords) => toCoords(vector, basis).Zip(coords, (x, y) => x * y).Sum();

}

Instead of providing the linear functional with a set of vector coordinates to operate on, I provided a vector and a way of getting coordinates from the vector in the form of a ToCoords delegate.

Notice that LinearFunctional is covariant in TBasis (i.e. <out TBasis>). This is due to the fact that ToCoords is <in TBasis> and I’m passing one in. Somehow two ins make an out. Maybe Erik can explain that...

So what happens to LinearFunctional<A> under a basis change from A to B? This one writes itself:

public static LinearFunctional<B> TransformLinearFunctional<A, B>(this LinearFunctional<A> lfa, Func<A, B> basisA2B)

{

return (vector, toCoords) => lfa(vector, TransformCoords(toCoords, basisA2B));

}

In order to go from a LinearFunctional<A> to a LinearFunctional<B> we need a function from A to B. This is the opposite of ToCoords. The behaviour under composition is also opposite to ToCoords (and the same as Select). The following expressions are equivalent:

LinearFunctional<C> lfc1 = lfa.TransformLinearFunctional(x => g(f(x)));

LinearFunctional<C> lfc2 = lfa.TransformLinearFunctional(f).TransformLinearFunctional(g);

Now once you get to tensors you can get even more exotic things that have both covariant and contravariant parts. I suspect this is the kind of thing Brian was trying to work with.

What have I missed? Is this too simplistic?

Steve.