What is the use of ScopedValues beyond web authorization?

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 :slight_smile:

1 Like

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 :slight_smile:.

5 Likes

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.

1 Like

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.

3 Likes

So is ScopedValues basically program-global variables that one can access from anywhere? (with a bonus of being thread-safe)

I had also prototype variants of Test/TimerOutputs that use a ScopedValue to propagate the current context to the next layer.

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.

3 Likes

Looks like there’s a whole JuliaCon talk (by @vchuravy ?) set aside for this topic: ScopedValues -- What are they good for :: Juliacon 2024 :: pretalx

1 Like

Indeed… So if you have an interesting/cool use-case please let me know.

2 Likes

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.

3 Likes

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.

1 Like

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?

1 Like

Yeah, Valentin mentioned above he experimented with that; I think his PR was Implement Test/Testset using ScopedValue by vchuravy · Pull Request #51012 · JuliaLang/julia · GitHub

1 Like