Reputation: 14501
I have a specific use case where I want a function to basically provide a warning the first time it is called to tell the user some info. Other than using a global counter and keeping track of the number of times the function is called, I am not sure how I can check this. Any ideas on specific Julia syntax that would allow me to check if the function is being called for the first time?
Upvotes: 3
Views: 440
Reputation: 12179
Use the maxlog
feature of the logging macros:
julia> function warnfirst(x, y)
@warn "This is the first time you called this" maxlog=1
return 2x + y
end
warnfirst (generic function with 1 method)
julia> warnfirst(1, 2)
┌ Warning: This is the first time you called this
└ @ Main REPL[1]:2
4
julia> warnfirst(1, 2)
4
julia> warnfirst(1.0, 2.0) # what about different specializations?
4.0
Upvotes: 4
Reputation: 20950
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.
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.
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.
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
Upvotes: 5