22
33module ScopedValues
44
5- export ScopedValue, with, @with
5+ export ScopedValue, LazyScopedValue, with, @with
66public get
77
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+
856"""
957 ScopedValue(x)
1058
@@ -40,17 +88,23 @@ julia> sval[]
4088 Scoped values were introduced in Julia 1.11. In Julia 1.8+ a compatible
4189 implementation is available from the package ScopedValues.jl.
4290"""
43- mutable struct ScopedValue{T}
91+ mutable struct ScopedValue{T} <: AbstractScopedValue{T}
4492 # NOTE this struct must be defined as mutable one since it's used as a key of
4593 # `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
4795 const default:: T
48- ScopedValue {T} () where T = new (false )
96+ ScopedValue {T} () where T = new {T} (false )
4997 ScopedValue {T} (val) where T = new {T} (true , val)
5098 ScopedValue (val:: T ) where T = new {T} (true , val)
5199end
52100
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 ()
54108
55109"""
56110 isassigned(val::ScopedValue)
@@ -72,34 +126,34 @@ julia> isassigned(b)
72126false
73127```
74128"""
75- function Base. isassigned (val:: ScopedValue )
76- val. has_default && return true
129+ function Base. isassigned (val:: AbstractScopedValue )
130+ hasdefault ( val) && return true
77131 scope = Core. current_scope ():: Union{Scope, Nothing}
78132 scope === nothing && return false
79133 return haskey ((scope:: Scope ). values, val)
80134end
81135
82- const ScopeStorage = Base. PersistentDict{ScopedValue , Any}
136+ const ScopeStorage = Base. PersistentDict{AbstractScopedValue , Any}
83137
84138struct Scope
85139 values:: ScopeStorage
86140end
87141
88142Scope (scope:: Scope ) = scope
89143
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
91145 val = convert (T, value)
92146 if parent === nothing
93147 return Scope (ScopeStorage (key=> val))
94148 end
95149 return Scope (ScopeStorage (parent. values, key=> val))
96150end
97151
98- function Scope (scope, pair:: Pair{<:ScopedValue } )
152+ function Scope (scope, pair:: Pair{<:AbstractScopedValue } )
99153 return Scope (scope, pair... )
100154end
101155
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 } ...)
103157 # Unroll this loop through recursion to make sure that
104158 # our compiler optimization support works
105159 return Scope (Scope (scope, pair1... ), pair2, pairs... )
@@ -115,19 +169,17 @@ function Base.show(io::IO, scope::Scope)
115169 else
116170 print (io, " , " )
117171 end
118- print (io, typeof (key), " @" )
172+ print (io, isa (key, ScopedValue) ? ScopedValue{ eltype (key)} : typeof (key), " @" )
119173 show (io, Base. objectid (key))
120174 print (io, " => " )
121175 show (IOContext (io, :typeinfo => eltype (key)), value)
122176 end
123177 print (io, " )" )
124178end
125179
126- struct NoValue end
127- const novalue = NoValue ()
128-
129180"""
130181 get(val::ScopedValue{T})::Union{Nothing, Some{T}}
182+ get(val::LazyScopedValue{T})::Union{Nothing, Some{T}}
131183
132184If the scoped value isn't set and doesn't have a default value,
133185return `nothing`. Otherwise returns `Some{T}` with the current
@@ -148,31 +200,36 @@ julia> isnothing(ScopedValues.get(b))
148200true
149201```
150202"""
151- function get (val:: ScopedValue {T} ) where {T}
203+ function get (val:: AbstractScopedValue {T} ) where {T}
152204 scope = Core. current_scope ():: Union{Scope, Nothing}
153205 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))
156208 end
157209 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 )
160212 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 )
163216 end
164217 return nothing
165218end
166219
167- function Base. getindex (val:: ScopedValue {T} ):: T where T
220+ function Base. getindex (val:: AbstractScopedValue {T} ):: T where T
168221 maybe = get (val)
169222 maybe === nothing && throw (KeyError (val))
170223 return something (maybe):: T
171224end
172225
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
176233 print (io, ' (' )
177234 v = get (val)
178235 if v === nothing
@@ -265,7 +322,7 @@ julia> with(() -> a[] * b[], a=>3, b=>4)
26532212
266323```
267324"""
268- function with (f, pair:: Pair{<:ScopedValue } , rest:: Pair{<:ScopedValue } ...)
325+ function with (f, pair:: Pair{<:AbstractScopedValue } , rest:: Pair{<:AbstractScopedValue } ...)
269326 @with (pair, rest... , f ())
270327end
271328with (@nospecialize (f)) = f ()
0 commit comments