TLDR: When generating code for ...Assign
traits in std::ops
with operands known to produce inconsistent-but-valid output types, how ought one to handle the output types to make it the least confusing?
Background:
A nice macro generates a general representation of a number. This representation includes coefficients of some numeric type T corresponding to the defining elements of some vector basis:
struct Number<T>(Vec<T>);
The macro also generates some Special utility types – essentially like sparse vectors on predetermined useful bases – like:
/// A special type with an a-coefficient
struct A<T>{ a: T }
/// A special type with a c-coefficient
struct C<T>{ c: T }
/// A special type with a d-coefficient
struct D<T>{ d: T }
/// A special type with a- and b-coefficients
struct AB<T>{ a: T, b: T }
/// A special type with a-, b- and c-coefficients
struct ABC<T>{ a: T, b: T, c: T }
// ..et. cetera
The nice macro also generates std::ops::Sub
and std::ops::Add
trait implementations by determining the associated Output types according to two rules:
1. When an in-basis coefficient is in the right-hand-side, the result is an instance of the left-hand-side specialized representation type:
AB<T> - A<T> = AB<T> // Basis: {a, b} + {a} = {a, b} ... can use same type
2. When an out-of-basis coefficient is in the right-hand-side, the result is a general representation type unless a known specialized type is exists matching the new basis:
AB<T> - C<T> = ABC<T>
// ^ Basis: {a,b} + {c} = {a,b,c} (basis matches another type)
AB<T> + D<T> = Number<T>
// ^ Basis: {a,b} + {d} = {a,b,c,d,...} (no basis-match, so use general type)
The Assign Problem
What should the nice macro do with the AddAssign
and SubAssign
traits?!
The solution above (using the general type for out-of-bounds basis operations) doesn't really work – obviously it can't change the type of the self
being assigned to.
For now, it uses a nan
-producing function to signify something wrong, but then, the basis behavior seems inconsistent:
AB<T> += D<T> -> AB::<T>::nan() // Basis: {a,b} += {d} ≠ {a,b}
Would it be less confusing to just not generate the ...Assign
traits for these cases at all? I feel like this is a cop-out somehow. Or does this nan
result help the user know they're writing code that attempts invalid operations?