作用域值
作用域值在Julia中提供了动态作用域的实现。
|
词汇范围与动态范围 词法范围是Julia中的默认行为。 在词法范围下,变量的范围由程序的词法(文本)结构决定。 在动态范围下,变量在程序执行期间绑定到最近的赋值。 |
作用域值的状态取决于程序的执行路径。 这意味着对于作用域值,您可以同时观察多个不同的值。
|
兼容性
Julia1.11在Julia1.11中引入了作用域值。 在Julia1.8+中,可以从包ScopedValues中获得兼容的实现。jl. |
在其最简单的形式,你可以创建一个 Scoped值,scoped值具有默认值,然后使用 与或 @与以进入新的动态范围。 新作用域将继承父作用域(并递归地从所有外部作用域)的所有值,所提供的作用域值优先于以前的定义。
我们先来看一个*lexical*scope的例子。 A 让 语句开始一个新的词法范围,其中的外部定义 x 被它的内在定义所掩盖。
x = 1
let x = 5
@show x # 5
end
@show x # 1
在以下示例中,由于Julia使用词法范围,因此变量 x 在…… f 指的是 x 在全局范围中定义,并输入 让 作用域不会更改值 f 观察。
x = 1
f() = @show x
let x = 5
f() # 1
end
f() # 1
现在使用一个 Scoped值,scoped值 我们可以使用*dynamic*scoping。
using Base.ScopedValues
x = ScopedValue(1)
f() = @show x[]
with(x=>5) do
f() # 5
end
f() # 1
注意,观察到的值 Scoped值,scoped值 是依赖于程序的执行路径。
使用一个 康斯特 变量指向一个范围内的值,并且可以设置多个值 Scoped值,scoped值只需一个电话 与.
using Base.ScopedValues
f() = @show a[]
g() = @show b[]
const a = ScopedValue(1)
const b = ScopedValue(2)
f() # a[] = 1
g() # b[] = 2
# Enter a new dynamic scope and set value.
with(a => 3) do
f() # a[] = 3
g() # b[] = 2
with(a => 4, b => 5) do
f() # a[] = 4
g() # b[] = 5
end
f() # a[] = 3
g() # b[] = 2
end
f() # a[] = 1
g() # b[] = 2
范围价值 提供的宏版本 与. 表达式 @与var=>val expr 评估结果 expr 在一个新的动态范围与 瓦尔 设置为 瓦尔. @与var=>val expr 相当于 用(var=>val)做expr结束. 然而, 与 需要一个零参数闭包或函数,这会导致一个额外的调用帧。 作为示例,请考虑以下函数 f:
using Base.ScopedValues
const a = ScopedValue(1)
f(x) = a[] + x
如果你想跑 f 在一个动态范围与 a 设置为 2,那么你可以使用 与:
with(() -> f(10), a=>2)
但是,这需要包装 f 在零参数函数中。 如果你想避免额外的调用帧,那么你可以使用 @与 宏:
@with a=>2 f(10)
|
注意动态范围由以下方式继承 |
在下面的示例中,我们在启动任务之前打开一个新的动态范围。 父任务和两个子任务在同一时间观察相同作用域值的独立值。
using Base.ScopedValues
import Base.Threads: @spawn
const scoped_val = ScopedValue(1)
@sync begin
with(scoped_val => 2)
@spawn @show scoped_val[] # 2
end
with(scoped_val => 3)
@spawn @show scoped_val[] # 3
end
@show scoped_val[] # 1
end
作用域值在整个作用域中是常量,但可以在作用域值中存储可变状态。 请记住,全局变量的通常警告适用于并发编程的上下文。
在作用域值中存储对可变状态的引用时也需要注意。 你可能想明确地 unshare mutable state。
using Base.ScopedValues
import Base.Threads: @spawn
const sval_dict = ScopedValue(Dict())
# Example of using a mutable value wrongly
@sync begin
# `Dict` is not thread-safe the usage below is invalid
@spawn (sval_dict[][:a] = 3)
@spawn (sval_dict[][:b] = 3)
end
@sync begin
# If we instead pass a unique dictionary to each
# task we can access the dictionaries race free.
with(sval_dict => Dict()) do
@spawn (sval_dict[][:a] = 3)
end
with(sval_dict => Dict()) do
@spawn (sval_dict[][:b] = 3)
end
end
例子:
在下面的示例中,我们使用作用域值在web应用程序中实现权限检查。 确定请求的权限后,将输入一个新的动态范围和scoped值 水平 被设置。 应用程序的其他部分可以查询作用域值,并将接收适当的值。 其他替代方案,如任务本地存储和全局变量,并不适合这种传播;我们唯一的选择是通过整个调用链线程值。
using Base.ScopedValues
const LEVEL = ScopedValue(:GUEST)
function serve(request, response)
level = isAdmin(request) ? :ADMIN : :GUEST
with(LEVEL => level) do
Threads.@spawn handle(request, response)
end
end
function open(connection::Database)
level = LEVEL[]
if level !== :ADMIN
error("Access disallowed")
end
# ... open connection
end
function handle(request, response)
# ...
open(Database(#=...=#))
# ...
end
成语
Unshare可变状态
using Base.ScopedValues
import Base.Threads: @spawn
const sval_dict = ScopedValue(Dict())
# If you want to add new values to the dict, instead of replacing
# it, unshare the values explicitly. In this example we use `merge`
# to unshare the state of the dictionary in parent scope.
@sync begin
with(sval_dict => merge(sval_dict[], Dict(:a => 10))) do
@spawn @show sval_dict[][:a]
end
@spawn sval_dict[][:a] = 3 # Not a race since they are unshared.
end
作为全局变量的作用域值
为了访问作用域值的值,作用域值本身必须在(词法)范围内。 这意味着通常您可能希望将作用域值用作常量全局变量。
using Base.ScopedValues
const sval = ScopedValue(1)
实际上,人们可以将作用域值视为隐藏的函数参数。
这并不排除它们作为非全局变量的使用。
using Base.ScopedValues
import Base.Threads: @spawn
function main()
role = ScopedValue(:client)
function launch()
#...
role[]
end
@with role => :server @spawn launch()
launch()
end
但是在这些情况下,直接传递函数参数可能会更简单。
空气污染指数文件
# *`基地。ScopedValues。Scoped值,scoped值`*-类型
ScopedValue(x)
创建一个跨动态范围传播值的容器。 使用方法 与创建并进入新的动态范围。
值只能在进入新的动态作用域时设置,并且引用的值在执行动态作用域期间将是恒定的。
动态范围跨任务传播。
*例子*
julia> using Base.ScopedValues;
julia> const sval = ScopedValue(1);
julia> sval[]
1
julia> with(sval => 2) do
sval[]
end
2
julia> sval[]
1
|
兼容性
Julia1.11在Julia1.11中引入了作用域值。 在Julia1.8+中,可以从包ScopedValues中获得兼容的实现。jl. |
# *`基地。ScopedValues。与`*-函数
with(f, (var::ScopedValue{T} => val)...)
执行 f 在一个新的动态范围与 瓦尔 设置为 瓦尔. 瓦尔 将转换为类型 T.
*例子*
julia> using Base.ScopedValues
julia> a = ScopedValue(1);
julia> f(x) = a[] + x;
julia> f(10)
11
julia> with(a=>2) do
f(10)
end
12
julia> f(10)
11
julia> b = ScopedValue(2);
julia> g(x) = a[] + b[] + x;
julia> with(a=>10, b=>20) do
g(30)
end
60
julia> with(() -> a[] * b[], a=>3, b=>4)
12
# *`基地。ScopedValues。@与`*-马科罗_
@with (var::ScopedValue{T} => val)... expr
宏版本的 与. 表达式 @与var=>val expr 评估结果 expr 在一个新的动态范围与 瓦尔 设置为 瓦尔. 瓦尔 将转换为类型 T. @与var=>val expr 相当于 用(var=>val)做expr结束,但是 @与 避免创建闭包。
*例子*
julia> using Base.ScopedValues
julia> const a = ScopedValue(1);
julia> f(x) = a[] + x;
julia> @with a=>2 f(10)
12
julia> @with a=>3 begin
x = 100
f(x)
end
103
# *`基地。is分配`*-Method
isassigned(val::ScopedValue)
测试是否 Scoped值,scoped值 具有赋值。
*例子*
julia> using Base.ScopedValues
julia> a = ScopedValue(1); b = ScopedValue{Int}();
julia> isassigned(a)
true
julia> isassigned(b)
false
# *`基地。ScopedValues。获取`*-函数
get(val::ScopedValue{T})::Union{Nothing, Some{T}}
如果未设置作用域值并且没有默认值,则返回 什么都没有. 否则返回 一些{T} 用电流值。
*例子*
julia> using Base.ScopedValues
julia> a = ScopedValue(42); b = ScopedValue{Int}();
朱莉娅>ScopedValues.获取(a)
一些(42)
朱莉娅>isnothing(ScopedValues.得到(b))
真的
实施说明和绩效
范围s使用持久字典。 查找和插入是 O(日志(32,n)) 在动态范围输入时,将复制少量数据,并在其他范围之间共享未更改的数据。
该 范围 对象本身不是面向用户的,可能会在Julia的未来版本中更改。
设计灵感
这个设计受到了很大的启发。https://openjdk.org/jeps/429[JEPS-429],这反过来又受到许多Lisp方言中动态范围的自由变量的启发。 特别是Interlisp-D及其深度结合策略。
先前讨论的设计是上下文变量ala PEPS-567并在Julia中实现为https://github.com/tkf/ContextVariablesX.jl[ContextVariablesX.jl]。