Skip to content

Commit 91da115

Browse files
CameronBieganekKristofferC
authored andcommitted
Make some improvements to the Scoped Values documentation. (#53628)
Fixes #53471. One thing to note, I changed the signature in the `with` docstring from this: ```julia with(f, (var::ScopedValue{T} => val::T)...) ``` to this: ```julia with(f, (var::ScopedValue{T} => val)...) ``` ...since the original signature in the docstring was too strict. I also added this sentence to the docstring: ```julia `val` will be converted to type `T`. ``` I added a couple tests that verify the conversion behavior. (cherry picked from commit 7613c69)
1 parent 0087a11 commit 91da115

File tree

3 files changed

+159
-45
lines changed

3 files changed

+159
-45
lines changed

base/scopedvalues.jl

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module ScopedValues
44

55
export ScopedValue, with, @with
6+
public get
67

78
"""
89
ScopedValue(x)
@@ -54,7 +55,22 @@ Base.eltype(::ScopedValue{T}) where {T} = T
5455
"""
5556
isassigned(val::ScopedValue)
5657
57-
Test whether a ScopedValue has an assigned value.
58+
Test whether a `ScopedValue` has an assigned value.
59+
60+
See also: [`ScopedValues.with`](@ref), [`ScopedValues.@with`](@ref), [`ScopedValues.get`](@ref).
61+
62+
# Examples
63+
```jldoctest
64+
julia> using Base.ScopedValues
65+
66+
julia> a = ScopedValue(1); b = ScopedValue{Int}();
67+
68+
julia> isassigned(a)
69+
true
70+
71+
julia> isassigned(b)
72+
false
73+
```
5874
"""
5975
function Base.isassigned(val::ScopedValue)
6076
val.has_default && return true
@@ -114,6 +130,21 @@ const novalue = NoValue()
114130
If the scoped value isn't set and doesn't have a default value,
115131
return `nothing`. Otherwise returns `Some{T}` with the current
116132
value.
133+
134+
See also: [`ScopedValues.with`](@ref), [`ScopedValues.@with`](@ref), [`ScopedValues.ScopedValue`](@ref).
135+
136+
# Examples
137+
```jldoctest
138+
julia> using Base.ScopedValues
139+
140+
julia> a = ScopedValue(42); b = ScopedValue{Int}();
141+
142+
julia> ScopedValues.get(a)
143+
Some(42)
144+
145+
julia> isnothing(ScopedValues.get(b))
146+
true
147+
```
117148
"""
118149
function get(val::ScopedValue{T}) where {T}
119150
scope = Core.current_scope()::Union{Scope, Nothing}
@@ -151,11 +182,32 @@ function Base.show(io::IO, val::ScopedValue)
151182
end
152183

153184
"""
154-
@with vars... expr
185+
@with (var::ScopedValue{T} => val)... expr
186+
187+
Macro version of `with`. The expression `@with var=>val expr` evaluates `expr` in a
188+
new dynamic scope with `var` set to `val`. `val` will be converted to type `T`.
189+
`@with var=>val expr` is equivalent to `with(var=>val) do expr end`, but `@with`
190+
avoids creating a closure.
191+
192+
See also: [`ScopedValues.with`](@ref), [`ScopedValues.ScopedValue`](@ref), [`ScopedValues.get`](@ref).
193+
194+
# Examples
195+
```jldoctest
196+
julia> using Base.ScopedValues
155197
156-
Macro version of `with(f, vars...)` but with `expr` instead of `f` function.
157-
This is similar to using [`with`](@ref) with a `do` block, but avoids creating
158-
a closure.
198+
julia> const a = ScopedValue(1);
199+
200+
julia> f(x) = a[] + x;
201+
202+
julia> @with a=>2 f(10)
203+
12
204+
205+
julia> @with a=>3 begin
206+
x = 100
207+
f(x)
208+
end
209+
103
210+
```
159211
"""
160212
macro with(exprs...)
161213
if length(exprs) > 1
@@ -172,9 +224,44 @@ macro with(exprs...)
172224
end
173225

174226
"""
175-
with(f, (var::ScopedValue{T} => val::T)...)
227+
with(f, (var::ScopedValue{T} => val)...)
228+
229+
Execute `f` in a new dynamic scope with `var` set to `val`. `val` will be converted
230+
to type `T`.
231+
232+
See also: [`ScopedValues.@with`](@ref), [`ScopedValues.ScopedValue`](@ref), [`ScopedValues.get`](@ref).
233+
234+
# Examples
235+
```jldoctest
236+
julia> using Base.ScopedValues
237+
238+
julia> a = ScopedValue(1);
176239
177-
Execute `f` in a new scope with `var` set to `val`.
240+
julia> f(x) = a[] + x;
241+
242+
julia> f(10)
243+
11
244+
245+
julia> with(a=>2) do
246+
f(10)
247+
end
248+
12
249+
250+
julia> f(10)
251+
11
252+
253+
julia> b = ScopedValue(2);
254+
255+
julia> g(x) = a[] + b[] + x;
256+
257+
julia> with(a=>10, b=>20) do
258+
g(30)
259+
end
260+
60
261+
262+
julia> with(() -> a[] * b[], a=>3, b=>4)
263+
12
264+
```
178265
"""
179266
function with(f, pair::Pair{<:ScopedValue}, rest::Pair{<:ScopedValue}...)
180267
@with(pair, rest..., f())

doc/src/base/scopedvalues.md

Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,15 @@ concurrently.
1717
Scoped values were introduced in Julia 1.11. In Julia 1.8+ a compatible
1818
implementation is available from the package ScopedValues.jl.
1919

20-
In its simplest form you can create a [`Base.ScopedValue`](@ref) with a
21-
default value and then use [`Base.with`](@ref with) or [`Base.@with`](@ref) to
22-
enter a new dynamic scope.
20+
In its simplest form you can create a [`ScopedValue`](@ref Base.ScopedValues.ScopedValue)
21+
with a default value and then use [`with`](@ref Base.ScopedValues.with) or
22+
[`@with`](@ref Base.ScopedValues.@with) to enter a new dynamic scope. The new scope will
23+
inherit all values from the parent scope (and recursively from all outer scopes) with the
24+
provided scoped value taking priority over previous definitions.
2325

24-
The new scope will inherit all values from the parent scope
25-
(and recursively from all outer scopes) with the provided scoped
26-
value taking priority over previous definitions.
27-
28-
Let's first look at an example of **lexical** scope:
29-
30-
A `let` statements begins a new lexical scope within which the outer definition
31-
of `x` is shadowed by it's inner definition.
26+
Let's first look at an example of **lexical** scope. A `let` statement begins
27+
a new lexical scope within which the outer definition of `x` is shadowed by
28+
it's inner definition.
3229

3330
```julia
3431
x = 1
@@ -38,9 +35,9 @@ end
3835
@show x # 1
3936
```
4037

41-
Since Julia uses lexical scope the variable `x` is bound within the function `f`
42-
to the global scope and entering a `let` scope does not change the value `f`
43-
observes.
38+
In the following example, since Julia uses lexical scope, the variable `x` in the body
39+
of `f` refers to the `x` defined in the global scope, and entering a `let` scope does
40+
not change the value `f` observes.
4441

4542
```julia
4643
x = 1
@@ -64,7 +61,7 @@ end
6461
f() # 1
6562
```
6663

67-
Not that the observed value of the `ScopedValue` is dependent on the execution
64+
Note that the observed value of the `ScopedValue` is dependent on the execution
6865
path of the program.
6966

7067
It often makes sense to use a `const` variable to point to a scoped value,
@@ -74,34 +71,54 @@ and you can set the value of multiple `ScopedValue`s with one call to `with`.
7471
```julia
7572
using Base.ScopedValues
7673

77-
const scoped_val = ScopedValue(1)
78-
const scoped_val2 = ScopedValue(0)
79-
80-
# Enter a new dynamic scope and set value
81-
@show scoped_val[] # 1
82-
@show scoped_val2[] # 0
83-
with(scoped_val => 2) do
84-
@show scoped_val[] # 2
85-
@show scoped_val2[] # 0
86-
with(scoped_val => 3, scoped_val2 => 5) do
87-
@show scoped_val[] # 3
88-
@show scoped_val2[] # 5
74+
f() = @show a[]
75+
g() = @show b[]
76+
77+
const a = ScopedValue(1)
78+
const b = ScopedValue(2)
79+
80+
f() # a[] = 1
81+
g() # b[] = 2
82+
83+
# Enter a new dynamic scope and set value.
84+
with(a => 3) do
85+
f() # a[] = 3
86+
g() # b[] = 2
87+
with(a => 4, b => 5) do
88+
f() # a[] = 4
89+
g() # b[] = 5
8990
end
90-
@show scoped_val[] # 2
91-
@show scoped_val2[] # 0
91+
f() # a[] = 3
92+
g() # b[] = 2
9293
end
93-
@show scoped_val[] # 1
94-
@show scoped_val2[] # 0
94+
95+
f() # a[] = 1
96+
g() # b[] = 2
9597
```
9698

97-
Since `with` requires a closure or a function and creates another call-frame,
98-
it can sometimes be beneficial to use the macro form.
99+
`ScopedValues` provides a macro version of `with`. The expression `@with var=>val expr`
100+
evaluates `expr` in a new dynamic scope with `var` set to `val`. `@with var=>val expr`
101+
is equivalent to `with(var=>val) do expr end`. However, `with` requires a zero-argument
102+
closure or function, which results in an extra call-frame. As an example, consider the
103+
following function `f`:
99104

100105
```julia
101106
using Base.ScopedValues
107+
const a = ScopedValue(1)
108+
f(x) = a[] + x
109+
```
110+
111+
If you wish to run `f` in a dynamic scope with `a` set to `2`, then you can use `with`:
102112

103-
const STATE = ScopedValue{State}()
104-
with_state(f, state::State) = @with(STATE => state, f())
113+
```julia
114+
with(() -> f(10), a=>2)
115+
```
116+
117+
However, this requires wrapping `f` in a zero-argument function. If you wish to avoid
118+
the extra call-frame, then you can use the `@with` macro:
119+
120+
```julia
121+
@with a=>2 f(10)
105122
```
106123

107124
!!! note
@@ -265,11 +282,11 @@ Base.@kwdef struct Configuration
265282
verbose::Bool = false
266283
end
267284

268-
const CONFIG = ScopedValue(Configuration())
285+
const CONFIG = ScopedValue(Configuration(color=true))
269286

270-
@with CONFIG => Configuration(CONFIG[], color=true) begin
287+
@with CONFIG => Configuration(color=CONFIG[].color, verbose=true) begin
271288
@show CONFIG[].color # true
272-
@show CONFIG[].verbose # false
289+
@show CONFIG[].verbose # true
273290
end
274291
```
275292

test/scopedvalues.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ emptyf() = nothing
5252
@testset "conversion" begin
5353
with(emptyf, sval_float=>2)
5454
@test_throws MethodError with(emptyf, sval_float=>"hello")
55+
a = ScopedValue(1)
56+
with(a => 2.0) do
57+
@test a[] == 2
58+
@test a[] isa Int
59+
end
60+
a = ScopedValue(1.0)
61+
with(a => 2) do
62+
@test a[] == 2.0
63+
@test a[] isa Float64
64+
end
5565
end
5666

5767
import Base.Threads: @spawn

0 commit comments

Comments
 (0)