The way the problem is decomposed doesn't feel right. In particular, there are too many interfaces, and Additive
, Divisible
, Negative
are unnecessarily disconnected.
I recommend to follow a more (mathematically) natural path. The Gram-Schmidt process works in any inner product space (which is by definition a vector space equipped with an inner product), so consider an
abstract class InnerProductSpace <V<AbelianGroup<V>, F>Field<F>> {
AbelianGroup<V> vectors;
Field<F> scalars;
V scale(V vector, F scalar);
F innerProduct(V v1, V v2);
V[] orthogonalize(V[] basis) {
// your Gram-Schmidt implementation here
}
}
As a side note, I wouldn't call an orthogonalization method GramShmidt
. As a client of this library I am not concerned with which process is used. The only thing I care about is that there is the method taking a basis and returning an orthogonalized one.
I also took a liberty to make a shortcut and not spell out the VectorSpace interface, to which a scale
method really belongs.
The Field
is what holds addition and multiplication together:
public interface Field<F> {
F add(F f1, F f2);
F mul(F f1, F f2);
F neg(F f);
F inv(F f);
}
It is OK to have sub
and div
instead of neg
and inv
.
Notice that the field must be closed under its operation. An addition (or multiplication) returning a type different than the type of arguments makes no mathematical sense.
I have to admit that I have no idea how to express other constraints a.k.a. field axioms. I doubt that it is possible.