ScopedValues are coming to Julia v1.11 (I think?). After reading a few examples, I think of them as resettable Refs. Apparently they behave like constants even though you can temporarily change their values. They are useful in passing data from one function to another without passing it “through” the functions (example below). And it seems like this is a neat way to do authorizations in web-based systems.
What are some of its uses beyond web-based systems?
using ScopedValues
const authorized = ScopedValue(false)
"""
the friend communicates to (the guard via) the messanger
the friend does not tell the messanger whether it is authorized
"""
function friend()
with(authorized => true) do
messanger()
end
return nothing
end
"""
the foe communicates to (the guard via) the messanger
the foe does not tell the messanger whether it is authorized
"""
function foe()
with(authorized => false) do
messanger()
end
return nothing
end
"""
the messanger talks to the guard on behalf of the seeker (friend or foe)
the messanger does not know whether the friend or foe invokes it
"""
function messanger()
guard()
return nothing
end
"""
the guard lets the messanger know whether the seeker may pass
"""
function guard()
granted =
if authorized[]
println("passage granted")
true
else
println("passage denied")
false
end
return granted
end
# friend()
# foe()
I’m using them in Supposition.jl to pass a context object that’s not exposed to the user-facing API into the code handling that API call. It’s pretty useful for that, because it lets me design the user API without having to leak internals
I’m using them in AllocArrays.jl to hold a bump allocator to allocate memory from. ScopeValues are very useful for this, since I have no way to pass extra arguments into similar(::Type{<:AllocArray{T}},...) calls made from library code I don’t control, but in my method for similar I can access a scoped value that holds the allocator to use to allocate the memory. This is nicer than a global since one can use different allocators in parallel (on different tasks) for different things, potentially from multiple downstream libraries that don’t know about each other but both use AllocArrays.jl.
Though this was a bit of a solution in search of a problem, since I had the idea to write AllocArrays after thinking about possible uses for ScopedValues .
Scoped values (aka dynamic variables) are useful any time you want to setup a context with dynamic extent. They are not (yet) widely used in Julia, but in Conditions.jl I used them to setup error handler (which are dynamically bound) and also some idioms such as redirect_stdout have similar effect (even though they predate scoped values and just temporarily overwrite the global binding instead).
For further inspiration you could look at Lisp where dynamic variables are more wide-spread, e.g., in Common Lisp or especially Emacs Lisp.
Probably one should mention that the logging system relies on them heavily, and the logging system (@info, @warn, etc) is used pretty often! Prior to scoped values, the logging state got a special field in the task object to enable dynamic scope for loggers specifically; the scoped values PR changed it into a generic system that anyone can use, and reimplemented logging as just a regular scoped value instead of a special thing that couldn’t be done outside of Base.
StyledStrings.jl uses ScopedValues to allow for the use of a seperate set of styles within a certain scope (withfaces). This could be done by editing then reversing the edits to a global variable, but that’s not thread-safe.
I’ve been thinking Makie could use this for themes, or for backend activation maybe, at least if we ever want to be able to render from multiple threads. But that’s in general hard to do with the global state in OpenGL I think.
I think themes are a good use-case. I think anytime there’s the pattern
library_function() do
# user does stuff
end
where library_function sets up some global state, then calls the user function to do stuff, then resets the state, would benefit from that global state being ScopedValues instead of raw global state. I think Mocking.jl does stuff like this too.
Thanks, makes a lot of sense.
Just saw that @testset – which also introduces a dynamic scope for tests – uses task_local_storage … could probably also be moved to scoped values?