AFAIU adding methods to @pure functions is not allowed. However I have a function mysin that I want to be able to add methods to later on and I also know that certain methods are pure. Is a purity fence like in the following snipped a sane solution to this?
using BenchmarkTools
Base.@pure mysin_pure(x::Float64) = sin(x)
mysin(x) = mysin_pure(x)
mysin(x::String) = some_cracy_method_added_later_on
function doit(f,n=100)
ret = 0.
for _ in 1:n
ret += f(1)
end
ret
end
@btime doit(sin)
@btime doit(mysin)
674.955 ns (0 allocations: 0 bytes)
52.307 ns (0 allocations: 0 bytes)
If so is it also a sane idea to add a @pure_method macro that expands
In short, disrecommended.
(because) You are asking “Is it reasonable to take a @pure function and then make it generic if it is done indirectly?”
A pure function can only depend on immutable information. This also means a @pure function cannot use any global mutable state, including generic functions. Calls to generic functions depend on method tables which are mutable global state. Use with caution, incorrect @pure annotation of a function may introduce hard to identify bugs. Double check for calls to generic functions.
Doing what you explore would violate documented tenants of @purity.
Actually, I think your proposal with mysin and mysin_pure looks totally fine. mysin is a regular generic function, and one of its methods happens to call an @pure function. That all sounds good to me.
I’m not as sure about your proposed macro–if you find yourself needing to use @pure that much, it might be a sign that there’s a better approach out there somewhere.
But I wonder if it is OK to wrap a generic function when all the arguments are concrete immutable and it’s “conceptually” pure (e.g., it doesn’t throw). I mean, is @jw3126’s example fine if sin doesn’t throw? Applying this point in the docstring literally probably indicates no:
This also means a @pure function cannot use any global mutable state, including generic functions.
But then I found this example where a @pure function calls a generic function which includes a path to throw but actually is impossible to throw inside the context of the @pure function: Rationale for the `@pure`ity of `Rational{T}(x)`. So maybe it’s fine to do this?
Base.@pure mysin_pure(x::Float64) = isfinite(x) ? sin(x) : NaN