Engee 文档

范围有限的值

该页面正在翻译中。

范围有限的值表示Julia中动态范围的实现。

!!! 注意"词汇范围与动态范围" 词法范围是Julia中的默认行为。 在区域的词法定义中,变量的区域由程序的词法(文本)结构决定。 动态定义作用域时,变量在程序执行期间绑定到最后一个赋值。

具有有限作用域的值的状态取决于程序的执行路径。 这意味着同一时间单个值可以有多个不同的值。

兼容性:Julia1.11

Julia1.11中出现了有限的范围值。 在Julia1.8及更高版本中,scopedvalues中提供了兼容的实现。jl包。

以最简单的形式 'ScopedValue'可以使用默认值创建,然后使用 '与''@with'添加新的动态区域。 新作用域继承父作用域(并递归地从所有外部作用域)的所有值,所提供的有限作用域值优先于以前的定义。

首先,让我们看一个*lexical*域的示例。 'Let’运算符启动一个新的词法域,其中`x`的外部定义被其内部定义所掩盖。

x = 1
let x = 5
    @show x # 5
end
@show x # 1

在以下示例中,由于Julia使用了词法作用域,因此函数`f’主体中的变量’x’引用了全局作用域中定义的变量’x`,并且添加作用域`let`不会改变函数`f’观察到的值。

x = 1
f() = @show x
let x = 5
    f() # 1
end
f() # 1

现在,使用’ScopedValue',您可以使用*dynamic*范围。

using Base.ScopedValues

x = ScopedValue(1)
f() = @show x[]
with(x=>5) do
    f() # 5
end
f() # 1

请注意,`ScopedValue’的观察值取决于程序执行路径。

使用`const`变量来指示具有有限范围的值通常是有意义的,并且可以通过一次调用`with`来设置几个`scopedvalues`的值。

using Base.ScopedValues

f() = @show a[]
g() = @show b[]

const a = ScopedValue(1)
const b = ScopedValue(2)

f() # a[] = 1
g() # b[] = 2

# Добавляем новую динамическую область и задаем значение.
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

'ScopedValues`为’with’提供宏版本。 表达式'@with var=>val expr在变量`var`的新动态区域中计算`expr,该变量设置为’val'。 +@with var⇒val expr+'等价于'+with(var⇒val)do expr end+'。 但是,'with’需要闭包或具有null参数的函数,这会导致额外的调用帧。 例如,考虑以下函数’f:

using Base.ScopedValues
const a = ScopedValue(1)
f(x) = a[] + x

要在动态范围内使用值为"2"的"a"运行函数"f`,请使用"with":

with(() -> f(10), a=>2)

但是,这需要将函数`f`包含在具有null参数的函数中。 为了避免出现额外的调用帧,您可以使用宏"@with":

@with a=>2 f(10)

动态区域由任务继承 `任务'在任务创建时。 动态区域*不是*通过`分布式分布。jl’操作。

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

using Base.ScopedValues
import Base.Threads: @spawn

const scoped_val=ScopedValue(1)
@同步开始
    带(scoped_val=>2)
        @spawn@show scoped_val[]#2
    结束
    带(scoped_val=>3)
        @spawn@show scoped_val[]#3
    结束
    @显示scoped_val[]#1
结束

具有有限区域的值在整个区域中是恒定的,但是可以在这样的值中存储可变状态。 请记住,在并行编程的上下文中,有关于全局变量的通常警告。

在有限范围的值中存储对可变状态的引用时也必须小心。 也许,在添加新的动态区域时,您会希望显式地 取消可更改状态的共享访问

using Base.ScopedValues
import Base.Threads: @spawn

const sval_dict = ScopedValue(Dict())

# Пример неправильного использования изменяемого значения
@sync begin
    # `Dict`не является потокобезопасным, приведенное ниже использование является недопустимым
    @spawn (sval_dict[][:a] = 3)
    @spawn (sval_dict[][:b] = 3)
end

@sync begin
    # Если вместо этого мы передадим каждой задаче уникальный словарь,
    # то сможем обращаться к словарям без гонок.
    with(sval_dict => Dict()) do
        @spawn (sval_dict[][:a] = 3)
    end
    with(sval_dict => Dict()) do
        @spawn (sval_dict[][:b] = 3)
    end
end

例子:

在下面的示例中,有限范围值用于在web应用程序中实现访问权限验证。 定义查询权限后,将添加一个新的动态范围,并设置一个范围有限的"级别"值。 应用程序的其他部分可以请求具有有限范围的值并接收相应的值。 其他替代方案,如任务本地存储和全局变量,不适合这种分布。 我们唯一的选择是通过整个调用链运行值。

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
    # ... открыть подключение
end

function handle(request, response)
    # ...
    open(Database(#=...=#))
    # ...
end

成语

撤销对可变状态的共享访问

using Base.ScopedValues
import Base.Threads: @spawn

const sval_dict = ScopedValue(Dict())

# Если вы хотите добавить новые значения в словарь, а не заменять
# их, явным образом отмените общий доступ к значениям. В этом примере мы используем `merge`
# для отмены общего доступа к состоянию словаря в родительской области.
@sync begin
    with(sval_dict => merge(sval_dict[], Dict(:a => 10))) do
        @spawn @show sval_dict[][:a]
    end
    @spawn sval_dict[][:a] = 3 # Гонка отсутствует, поскольку общий доступ отменен.
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

但也许在这种情况下,直接将参数传递给函数会更容易。

ScopedValue值太多

如果您为单个模块创建了很多`ScopedValue',那么使用特殊结构来存储它们可能会更好。

using Base.ScopedValues

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

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

@with CONFIG => Configuration(color=CONFIG[].color, verbose=true) begin
    @show CONFIG[].color # true
    @show CONFIG[].verbose # true
end

API文档

ScopedValue(x)

创建一个跨动态区域分发值的容器。 使用方法 'with'创建动态区域并进入。

这些值只能在进入一个新的动态区域时设置,并且它们在该区域的整个生命周期中都是恒定的。

动态区域适用于所有任务。

例子

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)...)

使用变量`var`在新的动态区域中执行`f`,该变量已被赋值为`val'。 `val’转换为类型’T'。

例子

julia> using Base.ScopedValues

julia> a = ScopedValue(1);

julia>f(x)=a[]+x;

朱莉娅>f(10)
11

julia>with(a=>2)do
           f(10)
       结束
12

朱莉娅>f(10)
11

朱莉娅>b=ScopedValue(2);

julia>g(x)=a[]+b[]+x;

julia>with(a=>10,b=>20)do
           g(30)
       结束
60

julia>with(()->a[]*b[],a=>3,b=>4)
12
@with (var::ScopedValue{T} => val)... expr

'With’版本作为宏。 表达式'@with var=>val expr在变量`var`的新动态区域中计算`expr,该变量设置为’val'。 val’转换为类型’T'。 `+@with var⇒val expr+'等价于'+with(var⇒val)do expr end+,但`@with`避免创建闭包。

例子

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)

检查’ScopedValue’是否已被赋值。

例子

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}}

如果未指定具有有限区域的值并且没有默认值,则返回’nothing'。 否则,它返回'Some{T}'与当前值。

例子

julia> using Base.ScopedValues

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

julia> ScopedValues.get(a)
Some(42)

julia> isnothing(ScopedValues.get(b))
true

实施说明和绩效

'Scope’使用永久字典。 搜索和插入使用`O(log(32,n))`执行。 当进入动态区域时,少量数据被复制,不变的数据分布在其他区域之间。

对象本身的"作用域"不适用于用户,可能会在Julia的未来版本中更改。

发展理念

这一发展深受该文件的影响https://openjdk.org/jeps/429 [JEPS-429],这反过来又依赖于许多Lisp方言中的动态域中的自由变量。 特别是在Interlisp-D及其深层链接策略中。

前面讨论了上下文变量的设计https://peps.python.org/pep-0567 /[PEPS-567],在Julia中实现为https://github.com/tkf/ContextVariablesX …​jl[ContextVariablesX]。