Dividing and multiplying units statically is where I've had trouble so far. I think I've found a way, but it would depend on negative trait bounds, as discussed here:
https://github.com/rust-lang/rfcs/issues/1053
Ideally, I'd like to be able to do something like this:
let a: Scalar<Joules> = Scalar::new(2.0);
let b: Scalar<Seconds> = Scalar::new(3.0);
let c: Scalar<Watts> = a / b;
// Watts is a type synonym for Over<Joules, Seconds>.
// Other derived units would use Times<U, V>. E.g.:
type Pascals = Times<Newton, Times<Meter, Meter>>.Systems will use an array of unit powers, so that if the array were defined as <Joules, Seconds, Newtons, Meters>, then acceleration would be <0,-2,0,1> and watts would be <1,-1,0,0>. Addition and subtraction require that your arrays are equal, and multiplication and division are pairwise additive/subtractive.
We would to resolve the nesting during multiplication and division. For example:
let a: Scalar<Times<A, B>> = Scalar::new(10.0);
let b: Scalar<Times<A, Times<B, C>>> = Scalar::new(5.0);
let c: Scalar<Times<A, Times<A, Times<B, Times<C>>>> = a * b;
Another challenge is that the type of c is ugly. But this could be mitigated by generous use of type synonyms. template <int m, int n, int l>
Matrix<m, l>
mul(Matrix<m, n> lhs, Matrix <n, l> rhs)
{...impl...}
And then be able to call it like int m, n, k, l = ... read from file or whatever
Matrix <m, n> m1 = ...;
Matrix <k, l> m2 = ...;
try {
Matrix <int o, int p> m3 = m1 * m2;
// ^ code compiled dynamically
// or loaded from cache, based on
// runtime types of m1 and m2.
// o and p set to the result
// of type inference.
// I could imagine even having
// specialised versions with inline
// assembly for specific dimensions.
} catch (DynamicCompilationException e) {
print("dimensions not compatible");
}
Java could be it, if it had reified generics. You'd create an implementation of Num, or load one from cache, then instantiate the template and attempt to call the mul function.Or you could abuse the invoke dynamic feature - create specialised functions matrixMultiply$m$n$l and classes Matrix$m$n from some other templating language as needed, then do an invoke dynamic based on type. But this would be very cumbersome to use, I think.
(The function that creates the array will be slower since it's not type-stable, but it can generate a type-stable function that's fast.)
case class Dim(size: Int)
trait Matrix[X <: Dim with Singleton, Y <: Dim with Singleton] {
def *[Z <: Dim with Singleton](other: Matrix[Y, Z]):Matrix[X, Z] = ???
}
object Matrix {
def apply[X <: Dim with Singleton, Y <: Dim with Singleton](x: X, y: Y):Matrix[X,Y] = ???
}
val x = Dim(100)
val y = Dim(readFromFile(...))
val z = Dim(37)
val A = Matrix[x.type, y.type](x, y)
val B = Matrix[y.type, z.type](y, z)
A * A // compile error
A * B // okIf I understand correctly the author remark about the lack of Rust support for value parameters, it seems that it should be equivalent to the Nim feature (static[int]) that has allowed me to write type-checked matrix operations there
http://maniagnosis.crsr.net/2015/07/abstracted-algebra-in-ru...
http://maniagnosis.crsr.net/2015/07/more-abstracted-algebra-...