1. 18
    1. 4

      As far as I can tell, the only systems that have a real answer to the commutativity problem (and similar) are term-rewriting systems using e-graphs: declare that x + y = y + x, and then try both expansions, picking whichever gives the ultimate result you like better.

      1. 1

        Rust could support “intelligent” commutative operations (to the extent of even distinguishing between allowed and disallowed addition/multiplication of matrices) with its recent improvements to const generics if it let you define a commutative trait like the ones in the article (that don’t compile) and then you used const math generic constraints to define the requirements on M and N as it’s purely based off the shape of a matrix that math is allowed.

        1. 2

          That is not a general solution. What do you do if x+y and y+x are both valid?

          1. 1

            Are they the same type or different types? If they’re the same type, then the operation will be forwarded to the impl with the lhs/rhs matching how the dev typed them. If they’re different types, you would have two separate impls (x lhs + y rhs, y lhs + x lhs) and the one matching how the dev typed it would be called. Unless I’m missing something.

    2. 1

      bytesize which I’ve been using in systemstat for a long time does something slightly interesting regarding various types – number + ByteSize impls are explicit for each numeric type via macros, but ByteSize + number is just one impl<T> Add<T> for ByteSize where T: Into<u64>.

      1. 1

        I mention in the article that I was able to do something similar where LHS is a Size, one generic impl<T> Mul<T> for Size where T: IntoIntermediate covers all the primitive integers but you need the separate impls for each primitive type as the RHS.

        I don’t use Into<u64> because a) PrettySize supports negative sizes (e.g. the difference between two sizes) so the “base” unit is i64, b) rust doesn’t provide impls for Into<uXX> from signed iXX values, c) I also support floating-point sources (e.g. Size::from_mib(1.1)) - all of which just means I have my own (sealed/private) trait called AsIntermediate that I implement via a macro for all the primitive signed, unsigned, and float types (except x128) which does a saturating conversion (e.g. u64::MAX becomes i64::MAX).

        I guess again unlike bytesize, I also have a second impl even for the LHS of Size case to support ops on a reference - you need for Size and for &Size separately (again because of rust’s orphan rule) since you can’t just do impl ... for Borrow<Size> to cover both Size and &Size (this is discussed briefly in the article).