ndarray icon indicating copy to clipboard operation
ndarray copied to clipboard

integration of type-level integers

Open kernelmachine opened this issue 8 years ago • 9 comments

Would be very interesting to use type-level integers (http://paholg.com/typenum/typenum/index.html) for things like matrix dimensions. For compile-time checking of matrix dimensions, indexing, etc.

kernelmachine avatar Mar 09 '16 13:03 kernelmachine

Aren't type level integers supposed to be integrated in the language at some point in the future? In my opinion it would probably be better to wait for them to be natively there.

While I like the feature of compile-time known dimensions, I also wonder how that could be implemented in current rust. In C++, eigen3 implements these using template specialization, but I don't know how that would be done in rust.

vbarrielle avatar Mar 09 '16 13:03 vbarrielle

More type checking is great to add, if it's possible and the benefits outweigh the costs.

ndarray is not focused on small matrices, so for that kind of use case there are much better crates (nalgebra for example).

A minor win we had during development was changing .slice() to take a reference to a fixed size array (&[Si; N]) instead of taking a &[Si], so that was a win for compile time type checking. It used an associated type to a type level property we already had (dimensionality) so it was an easy extension.

One type level property that already shows up in my code is memory order, which is quite useful to pass down through the layers of an algorithm. In the following example, it is just read from the stride data, and then it dispatches to the appropriate variant of the algorithm, continuing with type-level stride information.

pub fn process<A>(v: ArrayViewMut<A, Ix2>) {
    let (s0, s1) = (v.strides()[0], v.strides()[1]);
    if s1 == 1 {
        inner::<_, COrder>(v);
    } else if s0 == 1 {
        inner::<_, COrder>(v.reversed_axes());
    } else {
        inner::<_, MixedOrder>(v);
    }
}

Adding type parameters to a struct seems to me to always be an inconvenience though, especially to consumers of the data structure that want their code to simply be generic for all arrays. I simply don't know how this one works out in practice.

bluss avatar Mar 09 '16 15:03 bluss

How do we feel about (optional) type-level tagged markers, as in [1]? These allow dimensions to be mixed not if they have the same cardinality, but if they have the same tag. This avoids a different class of bugs, and does not use type level integers.

[1] https://play.rust-lang.org/?gist=4135d7ce4f1663380609&version=stable

daniel-vainsencher avatar Mar 12 '16 15:03 daniel-vainsencher

Any extra type parameter, even if it has a default, has some ergonomic cost. So I'm always skeptical. I have some experience from type level properties and indexing with indexing.

Merely avoiding such bugs will have less priority than possibly helping performance I think.

If it's shown to work well in practice, then it's of course something to consider. Right now I'm mulling over if it's possible to encode memory order in a marker type (and for backwards compat, default to dynamic memory order).

bluss avatar Mar 12 '16 19:03 bluss

It's tricky, if from_vec_dim is changed to return an OwnedArray<A, D, RowMajor> for example, this is a breaking change. There is also no way for us to make it coerce into OwnedArray<A, D, DynamicOrder> which could be the default.

bluss avatar Mar 12 '16 20:03 bluss

Encoding memory order doesn't really give us any algorithmic speedup, it just allows better static dispatch to the right algorithms, less dead code, smaller code.

bluss avatar Mar 12 '16 20:03 bluss

Another area i'm interested in is type-level markers encoding 'squareness' and 'singularity'.

Could be as simple as equality constraints on the indices of Dimension. Singularity may be out of scope for the library's purposes, but could be computed via rank, LU, det.

Again, no performance improvements, just better static dispatch/cleaner code for higher-level algorithms that may use ndarray.

kernelmachine avatar Apr 17 '16 13:04 kernelmachine

My concern is how do you even do that nicely in Rust?

if you have an array with type Array<f32, Ix, Square>, you'd want to be able to pass it to a function that takes an Array<f32, Ix> for example.

bluss avatar Apr 17 '16 16:04 bluss

I was just thinking about encoding shapes as const generics, as it could add some interesting type level checking. I really don't know how it should be the API designed afterwards, but if I understand things correctly, one could make it generic over the shape. The API concern is a pretty good one, so maybe should it be implemented as casting up/down as the developer responsibility (probably not that nice, but could be a workaround). As I see it, const generics are quite good today (although const context is a bit restrictive), so I wonder if there are plans in that direction.

fRbOl23 avatar Oct 15 '22 12:10 fRbOl23