As the author of ChangePrecision, I should caution you that it was intended for quick hacks and experiments, not for long-term usage. In the longer term, I tend to recommend writing your code more carefully to think about types and promotion.
Dealing with literals is the easy part of writing type-generic code, in my opinion. Mainly, you use things like rationals instead of decimal constants, and be a little careful with irrationals, e.g. write 2*(Ď€*x)
rather than 2Ď€ * x
, and there are a few cases where you need explicit casts.
The tricky part of type-generic code is dealing with type computations when the results are of different types than the inputs. For example, I would say that your example above
function foo(x::T) where T
let π=T(π)
x + 2Ď€
end
end
is simply wrong — it will give an error for foo(3)
, even though 3 + 2Ď€
is perfectly meaningful. You would instead want something like
foo(x::Number) = x + 2 * oftype(float(x), π)
or alternatively if you don’t mind a couple of extra multiplications:
foo(x::Number) = (x/2 + π)*2
Also tricky are cases where you need to allocate types for containers, and need to compute the correct type.
In practice, you usually don’t get type-generic code completely correct the first time, especially if you aren’t used to it, but over time the code becomes more generic as you try it with more types (e.g. complex numbers, dual numbers, …).