-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ScopedVariables are containers whose observed value depends the current dynamic scope. This implementation is inspired by https://openjdk.org/jeps/446 A scope is introduced with the `scoped` function that takes a lambda to execute within the new scope. The value of a `ScopedValue` is constant within that scope and can only be set upon introduction of a new scope. Scopes are propagated across tasks boundaries. Storage is implemented using a persistent dictionary.
- Loading branch information
Showing
13 changed files
with
631 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -163,7 +163,7 @@ | |
# result::Any | ||
# exception::Any | ||
# backtrace::Any | ||
# logstate::Any | ||
# scope::Any | ||
# code::Any | ||
#end | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -648,6 +648,11 @@ export | |
sprint, | ||
summary, | ||
|
||
# ScopedValue | ||
with, | ||
@with, | ||
ScopedValue, | ||
|
||
# logging | ||
@debug, | ||
@info, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# This file is a part of Julia. License is MIT: https://julialang.org/license | ||
|
||
module ScopedValues | ||
|
||
export ScopedValue, with, @with | ||
|
||
""" | ||
ScopedValue(x) | ||
Create a container that propagates values across dynamic scopes. | ||
Use [`with`](@ref) to create and enter a new dynamic scope. | ||
Values can only be set when entering a new dynamic scope, | ||
and the value referred to will be constant during the | ||
execution of a dynamic scope. | ||
Dynamic scopes are propagated across tasks. | ||
# Examples | ||
```jldoctest | ||
julia> const sval = ScopedValue(1); | ||
julia> sval[] | ||
1 | ||
julia> with(sval => 2) do | ||
sval[] | ||
end | ||
2 | ||
julia> sval[] | ||
1 | ||
``` | ||
!!! compat "Julia 1.11" | ||
Scoped values were introduced in Julia 1.11. In Julia 1.8+ a compatible | ||
implementation is available from the package ScopedValues.jl. | ||
""" | ||
mutable struct ScopedValue{T} | ||
const has_default::Bool | ||
const default::T | ||
ScopedValue{T}() where T = new(false) | ||
ScopedValue{T}(val) where T = new{T}(true, val) | ||
ScopedValue(val::T) where T = new{T}(true, val) | ||
end | ||
|
||
Base.eltype(::ScopedValue{T}) where {T} = T | ||
|
||
""" | ||
isassigned(val::ScopedValue) | ||
Test if the ScopedValue has a default value. | ||
""" | ||
Base.isassigned(val::ScopedValue) = val.has_default | ||
|
||
const ScopeStorage = Base.PersistentDict{ScopedValue, Any} | ||
|
||
mutable struct Scope | ||
values::ScopeStorage | ||
end | ||
|
||
function Scope(parent::Union{Nothing, Scope}, key::ScopedValue{T}, value) where T | ||
val = convert(T, value) | ||
if parent === nothing | ||
return Scope(ScopeStorage(key=>val)) | ||
end | ||
return Scope(ScopeStorage(parent.values, key=>val)) | ||
end | ||
|
||
function Scope(scope, pairs::Pair{<:ScopedValue}...) | ||
for pair in pairs | ||
scope = Scope(scope, pair...) | ||
end | ||
return scope::Scope | ||
end | ||
Scope(::Nothing) = nothing | ||
|
||
""" | ||
current_scope()::Union{Nothing, Scope} | ||
Return the current dynamic scope. | ||
""" | ||
current_scope() = current_task().scope::Union{Nothing, Scope} | ||
|
||
function Base.show(io::IO, scope::Scope) | ||
print(io, Scope, "(") | ||
first = true | ||
for (key, value) in scope.values | ||
if first | ||
first = false | ||
else | ||
print(io, ", ") | ||
end | ||
print(io, typeof(key), "@") | ||
show(io, Base.objectid(key)) | ||
print(io, " => ") | ||
show(IOContext(io, :typeinfo => eltype(key)), value) | ||
end | ||
print(io, ")") | ||
end | ||
|
||
struct NoValue end | ||
const novalue = NoValue() | ||
|
||
""" | ||
get(val::ScopedValue{T})::Union{Nothing, Some{T}} | ||
If the scoped value isn't set and doesn't have a default value, | ||
return `nothing`. Otherwise returns `Some{T}` with the current | ||
value. | ||
""" | ||
function get(val::ScopedValue{T}) where {T} | ||
# Inline current_scope to avoid doing the type assertion twice. | ||
scope = current_task().scope | ||
if scope === nothing | ||
isassigned(val) && return Some(val.default) | ||
return nothing | ||
end | ||
scope = scope::Scope | ||
if isassigned(val) | ||
return Some(Base.get(scope.values, val, val.default)::T) | ||
else | ||
v = Base.get(scope.values, val, novalue) | ||
v === novalue || return Some(v::T) | ||
end | ||
return nothing | ||
end | ||
|
||
function Base.getindex(val::ScopedValue{T})::T where T | ||
maybe = get(val) | ||
maybe === nothing && throw(KeyError(val)) | ||
return something(maybe)::T | ||
end | ||
|
||
function Base.show(io::IO, val::ScopedValue) | ||
print(io, ScopedValue) | ||
print(io, '{', eltype(val), '}') | ||
print(io, '(') | ||
v = get(val) | ||
if v === nothing | ||
print(io, "undefined") | ||
else | ||
show(IOContext(io, :typeinfo => eltype(val)), something(v)) | ||
end | ||
print(io, ')') | ||
end | ||
|
||
""" | ||
with(f, (var::ScopedValue{T} => val::T)...) | ||
Execute `f` in a new scope with `var` set to `val`. | ||
""" | ||
function with(f, pair::Pair{<:ScopedValue}, rest::Pair{<:ScopedValue}...) | ||
@nospecialize | ||
ct = Base.current_task() | ||
current_scope = ct.scope::Union{Nothing, Scope} | ||
ct.scope = Scope(current_scope, pair, rest...) | ||
try | ||
return f() | ||
finally | ||
ct.scope = current_scope | ||
end | ||
end | ||
|
||
with(@nospecialize(f)) = f() | ||
|
||
""" | ||
@with vars... expr | ||
Macro version of `with(f, vars...)` but with `expr` instead of `f` function. | ||
This is similar to using [`with`](@ref) with a `do` block, but avoids creating | ||
a closure. | ||
""" | ||
macro with(exprs...) | ||
if length(exprs) > 1 | ||
ex = last(exprs) | ||
exprs = exprs[1:end-1] | ||
elseif length(exprs) == 1 | ||
ex = only(exprs) | ||
exprs = () | ||
else | ||
error("@with expects at least one argument") | ||
end | ||
for expr in exprs | ||
if expr.head !== :call || first(expr.args) !== :(=>) | ||
error("@with expects arguments of the form `A => 2` got $expr") | ||
end | ||
end | ||
exprs = map(esc, exprs) | ||
quote | ||
ct = $(Base.current_task)() | ||
current_scope = ct.scope::$(Union{Nothing, Scope}) | ||
ct.scope = $(Scope)(current_scope, $(exprs...)) | ||
$(Expr(:tryfinally, esc(ex), :(ct.scope = current_scope))) | ||
end | ||
end | ||
|
||
end # module ScopedValues |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.