tl;dr: Some methods can return this
, an argument or one of the static Decimal.dConst
constants. If users mutate those, they might see unintended consequences. WAI or bug? If it's a bug, dealing with it in a performant way is complicated.
This is a contrived function, but bear with me.
function f(a: Decimal, b: Decimal): Decimal {
// Calculates 11a + 11b in a weird way.
const sum = a.add(b);
sum.exponent++;
// Sum is now 10(a+b) = 10a + 10b.
return sum.add(a).add(b);
}
This function works as expected... until b
is 0. Then the first .add will return a
as-is, setting sum
to be a
. The exponent increment multiplies a
by 10, and the final return is 20a
. This also mutates the value of a
which is probably unexpected to anyone calling this function. The same applies if a
is 0, as the first .add will return b
in that case.
A similar thing happens with any function that returns this
, an argument, or one of the static Decimal.dConst
constants. If users mutate those, they might see unintended consequences - especially the constants, as they are assumed to be immutable and widely used in internal break_eternity functions.
Is this working as intended, or is this a bug in break_eternity?
If this is a bug in break_eternity, then we'll need to create copies (and therefore new allocations) of all return values if they could be one of the aforementioned "should not be mutated" values...
...but copies would presumably slow down code - even code which doesn't mutate Decimals...
...so a user-controlled switch could be added to say "I swear I will never mutate a Decimal"...
...but then internal break_eternity code like d_lambertw
, which doesn't mutate decimals, will be slowed down if that is false...
...unless internal code has an override for the above switch which always sets it to false while inside of an internal break_eternity function...
...which will cause problems if the function throws an error, as the user's original choice wouldn't be restored...
...but that could be caught using a try/except
...
...but try/except
would presumably slow down code.
I haven't benchmarked the exact slowdown of (creating copies of Decimals all the time) vs. (giving users the option to not create copies) vs (always setting that to "don't copy" inside internal functions, with a try/except
statement to restore the user's choice). Each additional performance mitigation introduces more and more complicated code, which is harder to maintain.
Perhaps it's worth it to enforce immutability by deprecating the .fromX
methods and properties, instead of making copies selectively?