Skip to content

Scoped Values documentation #53471

@CameronBieganek

Description

@CameronBieganek

I've started reading the Scoped Values page in the manual (v1.12-dev). It seems a bit rough around the edges.

1.

Since Julia uses lexical scope the variable x is bound within the function f to the global scope and entering a let scope does not change the value f observes.

This sentence seems to refer to the code block that immediately proceeds it, but it actually refers to the next code block. So it should probably say something like

In the following example, the variable x is bound within ...

I don't want to propose an exact wording, I just want to point out that it's pretty confusing the way that it is written now.

2.

This example does not appear to be a good example of dynamic scoping:

const scoped_val = ScopedValue(1)
const scoped_val2 = ScopedValue(0)

# Enter a new dynamic scope and set value
@show scoped_val[] # 1
@show scoped_val2[] # 0
with(scoped_val => 2) do
    @show scoped_val[] # 2
    @show scoped_val2[] # 0
    with(scoped_val => 3, scoped_val2 => 5) do
        @show scoped_val[] # 3
        @show scoped_val2[] # 5
    end
    @show scoped_val[] # 2
    @show scoped_val2[] # 0
end
@show scoped_val[] # 1
@show scoped_val2[] # 0

The exact same behavior can be achieved with let blocks:

const scoped_val = 1
const scoped_val2 = 0

@show scoped_val # 1
@show scoped_val2 # 0
let scoped_val = 2
    @show scoped_val # 2
    @show scoped_val2 # 0
    let scoped_val = 3, scoped_val2 = 5
        @show scoped_val # 3
        @show scoped_val2 # 5
    end
    @show scoped_val # 2
    @show scoped_val2 # 0
end
@show scoped_val # 1
@show scoped_val2 # 0

(They print exactly the same thing to the REPL.)

3.

Since with requires a closure or a function and creates another call-frame, it can sometimes be beneficial to use the macro form.

const STATE = ScopedValue{State}()
with_state(f, state::State) = @with(STATE => state, f())

That sentence could use some elaboration, and I have no idea what the purpose of the example is (there is no explanation given).

4.

If I'm not mistaken, the @with in this example,

Base.@kwdef struct Configuration
    color::Bool = false
    verbose::Bool = false
end

const CONFIG = ScopedValue(Configuration())

@with CONFIG => Configuration(CONFIG[], color=true) begin
    @show CONFIG[].color # true
    @show CONFIG[].verbose # false
end

should actually be this:

@with CONFIG => Configuration(color=true) begin
    @show CONFIG[].color # true
    @show CONFIG[].verbose # false
end

or maybe this:

@with CONFIG => Configuration(verbose=CONFIG[].verbose, color=true) begin
    @show CONFIG[].color # true
    @show CONFIG[].verbose # false
end

5.

In the "Example" section, there is this sentence:

Other alternatives like task-local storage and global variables are not well suited for this kind of propagation; our only alternative would have been to thread a value through the entire call-chain.

Threading a value through the entire call-chain doesn't seem so bad to me. It's explicit (explicit is better than implicit) and it does not require a new language feature. Maybe you can make a better case in the manual for why ScopedValues are needed?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions