Here are some ideas for how to do this when you have the option of rewriting the function body. All of these could also be realized by writing a rather simple macro performing the respective transformation. OK, it's not so trivial if you want to get top-level and local definitions working right.
(Non-)option 1
Conceptually, you could do this with a generated function, and it will work mostly when you try it out:
julia> @generated function dostuff(x)
@warn "You really shouldn't do stuff!"
return :(2x + 1)
end
dostuff (generic function with 1 method)
julia> dostuff(1)
┌ Warning: You really shouldn't do stuff!
└ @ Main REPL[1]:2
3
julia> dostuff(1)
3
But: don't. The compiler is free to choose when to call the "generator", and to quote the docs: it is undefined exactly when, how often or how many times these side-effects will occur. Not a good idea.
Additionally, it is questionable whether @warn will use a printing function that is allowed within a generated function. In earlier Julias, using println instead of Core.println sometimes errored in generated functions, because the former modified the event loop.
Option 2
So for something better. Instead of your idea with a global counter, you can do something similar by defining the function as a closure of a let-bound variable:
julia> let isfirstcall = Threads.Atomic{Bool}(true)
global function dostuff(x)
if Threads.atomic_xchg!(isfirstcall, false)
@warn "You really shouldn't do stuff!"
end
return 2x + 1
end
end
dostuff (generic function with 1 method)
julia> dostuff(1)
┌ Warning: You really shouldn't do stuff!
└ @ Main REPL[16]:4
3
julia> dostuff(1)
3
julia> isfirstcall
ERROR: UndefVarError: isfirstcall not defined
I have here chosen to use atomics just for the fun of atomic_xchg!, but if threading is not an issue, a plain boolean will be fine, too.
Option 3
Also, while avoidable, a global variable isn't too bad if you do it right. Which means: make it a const Ref. And (optionally, but recommended in this case), use a var string to give it a name not usually accessible to the user:
julia> const var"##isfirstcall" = Ref(true)
julia> function dostuff(x)
if var"##isfirstcall"[]
@warn "You really shouldn't do stuff!"
var"##isfirstcall"[] = false
end
return 2x + 1
end
dostuff (generic function with 1 method)
julia> dostuff(1)
┌ Warning: You really shouldn't do stuff!
└ @ Main REPL[22]:3
3
julia> dostuff(1)
3