2
2
3
3
module ScopedValues
4
4
5
- export ScopedValue, with, @with
5
+ export ScopedValue, LazyScopedValue, with, @with
6
6
public get
7
7
8
+ """
9
+ AbstractScopedValue{T}
10
+
11
+ Abstract base type for scoped values that propagate values across
12
+ dynamic scopes. All scoped value types must extend this abstract type.
13
+
14
+ See also: [`ScopedValue`](@ref), [`LazyScopedValue`](@ref)
15
+
16
+ !!! compat "Julia 1.13"
17
+ AbstractScopedValue requires Julia 1.13+.
18
+ """
19
+ abstract type AbstractScopedValue{T} end
20
+
21
+
22
+ """
23
+ LazyScopedValue{T}(f::OncePerProcess{T})
24
+
25
+ A scoped value that uses an `OncePerProcess{T}` to lazily compute its default value
26
+ when none has been set in the current scope. Unlike `ScopedValue`, the default is
27
+ not evaluated at construction time but only when first accessed.
28
+
29
+ # Examples
30
+
31
+ ```julia-repl
32
+ julia> using Base.ScopedValues;
33
+
34
+ julia> const editor = LazyScopedValue(OncePerProcess(() -> ENV["JULIA_EDITOR"]));
35
+
36
+ julia> editor[]
37
+ "vim"
38
+
39
+ julia> with(editor => "emacs") do
40
+ sval[]
41
+ end
42
+ "emacs"
43
+
44
+ julia> editor[]
45
+ "vim"
46
+ ```
47
+
48
+ !!! compat "Julia 1.13"
49
+ LazyScopedValue requires Julia 1.13+.
50
+ """
51
+ mutable struct LazyScopedValue{T} <: AbstractScopedValue{T}
52
+ const getdefault:: OncePerProcess{T}
53
+ end
54
+
55
+
8
56
"""
9
57
ScopedValue(x)
10
58
@@ -40,17 +88,23 @@ julia> sval[]
40
88
Scoped values were introduced in Julia 1.11. In Julia 1.8+ a compatible
41
89
implementation is available from the package ScopedValues.jl.
42
90
"""
43
- mutable struct ScopedValue{T}
91
+ mutable struct ScopedValue{T} <: AbstractScopedValue{T}
44
92
# NOTE this struct must be defined as mutable one since it's used as a key of
45
93
# `ScopeStorage` dictionary and thus needs object identity
46
- const has_default :: Bool # this field is necessary since isbitstype `default` field may be initialized with undefined value
94
+ const hasdefault :: Bool # this field is necessary since isbitstype `default` field may be initialized with undefined value
47
95
const default:: T
48
- ScopedValue {T} () where T = new (false )
96
+ ScopedValue {T} () where T = new {T} (false )
49
97
ScopedValue {T} (val) where T = new {T} (true , val)
50
98
ScopedValue (val:: T ) where T = new {T} (true , val)
51
99
end
52
100
53
- Base. eltype (:: ScopedValue{T} ) where {T} = T
101
+ Base. eltype (:: AbstractScopedValue{T} ) where {T} = T
102
+
103
+ hasdefault (val:: ScopedValue ) = val. hasdefault
104
+ hasdefault (val:: LazyScopedValue ) = true
105
+
106
+ getdefault (val:: ScopedValue ) = val. hasdefault ? val. default : throw (KeyError (val))
107
+ getdefault (val:: LazyScopedValue ) = val. getdefault ()
54
108
55
109
"""
56
110
isassigned(val::ScopedValue)
@@ -72,34 +126,34 @@ julia> isassigned(b)
72
126
false
73
127
```
74
128
"""
75
- function Base. isassigned (val:: ScopedValue )
76
- val. has_default && return true
129
+ function Base. isassigned (val:: AbstractScopedValue )
130
+ hasdefault ( val) && return true
77
131
scope = Core. current_scope ():: Union{Scope, Nothing}
78
132
scope === nothing && return false
79
133
return haskey ((scope:: Scope ). values, val)
80
134
end
81
135
82
- const ScopeStorage = Base. PersistentDict{ScopedValue , Any}
136
+ const ScopeStorage = Base. PersistentDict{AbstractScopedValue , Any}
83
137
84
138
struct Scope
85
139
values:: ScopeStorage
86
140
end
87
141
88
142
Scope (scope:: Scope ) = scope
89
143
90
- function Scope (parent:: Union{Nothing, Scope} , key:: ScopedValue {T} , value) where T
144
+ function Scope (parent:: Union{Nothing, Scope} , key:: AbstractScopedValue {T} , value) where T
91
145
val = convert (T, value)
92
146
if parent === nothing
93
147
return Scope (ScopeStorage (key=> val))
94
148
end
95
149
return Scope (ScopeStorage (parent. values, key=> val))
96
150
end
97
151
98
- function Scope (scope, pair:: Pair{<:ScopedValue } )
152
+ function Scope (scope, pair:: Pair{<:AbstractScopedValue } )
99
153
return Scope (scope, pair... )
100
154
end
101
155
102
- function Scope (scope, pair1:: Pair{<:ScopedValue } , pair2:: Pair{<:ScopedValue } , pairs:: Pair{<:ScopedValue } ...)
156
+ function Scope (scope, pair1:: Pair{<:AbstractScopedValue } , pair2:: Pair{<:AbstractScopedValue } , pairs:: Pair{<:AbstractScopedValue } ...)
103
157
# Unroll this loop through recursion to make sure that
104
158
# our compiler optimization support works
105
159
return Scope (Scope (scope, pair1... ), pair2, pairs... )
@@ -115,19 +169,17 @@ function Base.show(io::IO, scope::Scope)
115
169
else
116
170
print (io, " , " )
117
171
end
118
- print (io, typeof (key), " @" )
172
+ print (io, isa (key, ScopedValue) ? ScopedValue{ eltype (key)} : typeof (key), " @" )
119
173
show (io, Base. objectid (key))
120
174
print (io, " => " )
121
175
show (IOContext (io, :typeinfo => eltype (key)), value)
122
176
end
123
177
print (io, " )" )
124
178
end
125
179
126
- struct NoValue end
127
- const novalue = NoValue ()
128
-
129
180
"""
130
181
get(val::ScopedValue{T})::Union{Nothing, Some{T}}
182
+ get(val::LazyScopedValue{T})::Union{Nothing, Some{T}}
131
183
132
184
If the scoped value isn't set and doesn't have a default value,
133
185
return `nothing`. Otherwise returns `Some{T}` with the current
@@ -148,31 +200,36 @@ julia> isnothing(ScopedValues.get(b))
148
200
true
149
201
```
150
202
"""
151
- function get (val:: ScopedValue {T} ) where {T}
203
+ function get (val:: AbstractScopedValue {T} ) where {T}
152
204
scope = Core. current_scope ():: Union{Scope, Nothing}
153
205
if scope === nothing
154
- val. has_default && return Some {T} (val . default)
155
- return nothing
206
+ ! hasdefault ( val) && return nothing
207
+ return Some {T} ( getdefault (val))
156
208
end
157
209
scope = scope:: Scope
158
- if val. has_default
159
- return Some {T} (Base. get (scope . values , val, val . default ):: T )
210
+ if hasdefault ( val)
211
+ return Some {T} (Base. get (Base . Fix1 (getdefault , val), scope . values, val ):: T )
160
212
else
161
- v = Base. get (scope. values, val, novalue)
162
- v === novalue || return Some {T} (v:: T )
213
+ v = Base. KeyValue. get (scope. values, val)
214
+ v === nothing && return nothing
215
+ return Some {T} (only (v):: T )
163
216
end
164
217
return nothing
165
218
end
166
219
167
- function Base. getindex (val:: ScopedValue {T} ):: T where T
220
+ function Base. getindex (val:: AbstractScopedValue {T} ):: T where T
168
221
maybe = get (val)
169
222
maybe === nothing && throw (KeyError (val))
170
223
return something (maybe):: T
171
224
end
172
225
173
- function Base. show (io:: IO , val:: ScopedValue )
174
- print (io, ScopedValue)
175
- print (io, ' {' , eltype (val), ' }' )
226
+ function Base. show (io:: IO , val:: AbstractScopedValue )
227
+ if isa (val, ScopedValue)
228
+ print (io, ScopedValue)
229
+ print (io, ' {' , eltype (val), ' }' )
230
+ else
231
+ print (io, typeof (val))
232
+ end
176
233
print (io, ' (' )
177
234
v = get (val)
178
235
if v === nothing
@@ -265,7 +322,7 @@ julia> with(() -> a[] * b[], a=>3, b=>4)
265
322
12
266
323
```
267
324
"""
268
- function with (f, pair:: Pair{<:ScopedValue } , rest:: Pair{<:ScopedValue } ...)
325
+ function with (f, pair:: Pair{<:AbstractScopedValue } , rest:: Pair{<:AbstractScopedValue } ...)
269
326
@with (pair, rest... , f ())
270
327
end
271
328
with (@nospecialize (f)) = f ()
0 commit comments