I didn’t spend that much time looking at BigInt
(@rfourquet was going to fix that), but yes, for BigFloat
, you can just use a bitstype or immutable, and they will never be mutated.
For BigInt, it might be better anyway to have a pure Julia implementation
What I’d implemented in that Gist gave a FloatRef
type, that was an efficient wrapper of MPFR
values, they are not immutable numbers, but rather containers for an MPFR float, with finalizers. They are very useful for doing operations such as summing up a vector or an array.
Then I also had fixed precision (parameterized) big floats, which worked by copying things quickly into MPFR “containers”, calling the MPFR function, and then creating a new fixed precision immutable value from the MPFR result.
(This was why I was hoping you would handle the issue in #18466, that I’d asked you about at JuliaCon, so that the parameter could simply be the # of bits, and the number of limbs for an NTuple{} field could be calculated from the typevar)
It’s also possible to handle the current BigFloat
, without needing finalizers, also by using some previously allocated containers to pass things back and forth between Julia and MPFR. In this case, the precision would get saved as a field in the type, instead of being a parameter.
Something that hasn’t come up and isn’t solved by this is how to performantly handle the difference between user functions which mutate and those that don’t. In DiffEq this is the difference between du=f(t,u)
and f!(t,u,du)
. Other packages also have a difference between the in-place syntax and the not-in-place syntax. So while the rest of the function will be the same using :=
(or something like it, an enhanced .=
), the function calls still have to be different depending on if it’s a mutable or immutable, since you cannot “see the changed” du
in the mutated form. Or is it okay to do something like
du = f!(t,u,du) # Maybe mutates du if it's mutable, or at least it returns dt
Could that hinder performance in the case where it is mutable? It seems weird and error-prone too.
Yes, it looks like we’d need to redefine the agreed upon meaning of foo!(x, ...)
from “mutates x
” to “returns updated x
, may mutate the originally passed x
”. I.e. you’d always need to call it as x = foo!(x, ...)
. Then we’d need to make sure every !
function actually returns the mutated object and every call actually assigns the result. I don’t have especially high hopes that we could pull this off smoothly.
I recently came across this again and thought that foo!!(x)
is a reasonable convention for “function that returns updated x
and may mutate the originally passed x
”. It’s not the prettiest thing, but it’s easy to type and it makes it clear that something weird is going on.
There is also an accidental parallel with mathematics: n!
is the product of all integers from 1
to n
, whereas n!!
is the product of every other integer in the same range. Similarly f!
would mutate always, whereas f!!
would mutate only when possible/appropriate.