AnyMath 文档

作用域值

作用域值在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)

注意动态范围由以下方式继承 任务s,在任务创建的时刻。 动态范围*不*通过传播 分布。jl 行动。

在下面的示例中,我们在启动任务之前打开一个新的动态范围。 父任务和两个子任务在同一时间观察相同作用域值的独立值。

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值对于一个给定的模块,使用专用结构来保存它们可能会更好。

using Base.ScopedValues

Base.@kwdef struct Configuration
    color::Bool = false
    verbose::Bool = false
end

const CONFIG = ScopedValue(Configuration(color=true))

@与配置=>配置(颜色=配置[]。color,verbose=true)开始
    @显示配置[]。颜色#真
    @显示配置[]。详细#真
结束

空气污染指数文件

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.

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
@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
isassigned(val::ScopedValue)

测试是否 Scoped值,scoped值 具有赋值。

*例子*

julia> using Base.ScopedValues

julia> a = ScopedValue(1); b = ScopedValue{Int}();

julia> isassigned(a)
true

julia> isassigned(b)
false
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]。