Scoped Values
Scoped values provide an implementation of dynamic scoping in Julia.
Lexical scoping Julia'da varsayılan davranıştır. Sözdizimsel kapsam altında bir değişkenin kapsamı, bir programın sözdizimsel (metinsel) yapısı tarafından belirlenir. Dinamik kapsam altında bir değişken, programın yürütülmesi sırasında en son atanan değere bağlıdır.
Kapsamlı bir değerin durumu, programın yürütme yoluna bağlıdır. Bu, kapsamlı bir değer için aynı anda birden fazla farklı değeri gözlemleyebileceğiniz anlamına gelir.
Scoped değerler Julia 1.11'de tanıtıldı. Julia 1.8+ sürümünde, ScopedValues.jl paketinden uyumlu bir uygulama mevcuttur.
En en basit formda, varsayılan bir değer ile ScopedValue oluşturabilirsiniz ve ardından with veya @with kullanarak yeni bir dinamik kapsam girebilirsiniz. Yeni kapsam, sağlanan kapsamlı değerin önceki tanımlamalar üzerinde önceliğe sahip olduğu ile birlikte, üst kapsamdan (ve tüm dış kapsamdan) tüm değerleri miras alacaktır.
Öncelikle leksikal kapsamın bir örneğine bakalım. Bir let ifadesi, dıştaki x tanımının içteki tanım tarafından gölgelenmiş olduğu yeni bir leksikal kapsam başlatır.
x = 1
let x = 5
@show x # 5
end
@show x # 1Aşağıdaki örnekte, Julia'nın sözcüksel kapsam kullandığı için, f fonksiyonunun gövdesindeki x değişkeni, küresel kapsamda tanımlanan x'e atıfta bulunur ve bir let kapsamına girmek, f'nin gözlemlediği değeri değiştirmez.
x = 1
f() = @show x
let x = 5
f() # 1
end
f() # 1Artık bir ScopedValue kullanarak dinamik kapsamı kullanabiliriz.
using Base.ScopedValues
x = ScopedValue(1)
f() = @show x[]
with(x=>5) do
f() # 5
end
f() # 1Gözlemlenen ScopedValue değerinin programın yürütme yoluna bağlı olduğunu unutmayın.
Bir const değişkeninin bir kapsamlı değere işaret etmesi genellikle mantıklıdır ve bir with çağrısıyla birden fazla ScopedValue'nın değerini ayarlayabilirsiniz.
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[] = 2ScopedValues, with makrosunun bir versiyonunu sağlar. @with var=>val expr ifadesi, var'ı val olarak ayarlayarak expr'yi yeni bir dinamik kapsamda değerlendirir. @with var=>val expr, with(var=>val) do expr end ile eşdeğerdir. Ancak, with sıfır argümanlı bir kapanış veya fonksiyon gerektirir, bu da ekstra bir çağrı çerçevesi ile sonuçlanır. Örnek olarak, aşağıdaki f fonksiyonunu düşünün:
using Base.ScopedValues
const a = ScopedValue(1)
f(x) = a[] + xEğer f'yi dinamik bir kapsamda a'yı 2 olarak ayarlamak istiyorsanız, with kullanabilirsiniz:
with(() -> f(10), a=>2)Ancak, bu f'yi sıfır-argümanlı bir fonksiyonun içine sarmayı gerektirir. Ek çağrı çerçevesinden kaçınmak istiyorsanız, @with makrosunu kullanabilirsiniz:
@with a=>2 f(10)Dinamik kapsamlar, görev oluşturma anında Task'lar tarafından miras alınır. Dinamik kapsamlar, Distributed.jl işlemleri aracılığıyla yayılmaz.
Aşağıdaki örnekte, bir görevi başlatmadan önce yeni bir dinamik kapsam açıyoruz. Ana görev ve iki çocuk görev, aynı anda aynı kapsamlı değerin bağımsız değerlerini gözlemliyor.
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
endKapsamlı değerler bir kapsam boyunca sabittir, ancak bir kapsamlı değerde değişken durum saklayabilirsiniz. Sadece, küresel değişkenler için geçerli olan olağan uyarıların eşzamanlı programlama bağlamında geçerli olduğunu unutmayın.
Dikkat, kapsamlı değerlerde değişken duruma referanslar saklarken de gereklidir. Yeni bir dinamik kapsam girdiğinizde açıkça unshare mutable state kullanmak isteyebilirsiniz.
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
endExample
Aşağıdaki örnekte, bir web uygulamasında bir izin kontrolü uygulamak için bir kapsamlı değer kullanıyoruz. İsteğin izinlerini belirledikten sonra, yeni bir dinamik kapsam girilir ve kapsamlı değer LEVEL ayarlanır. Uygulamanın diğer bölümleri kapsamlı değeri sorgulayabilir ve uygun değeri alır. Görev yerel depolama ve küresel değişkenler gibi diğer alternatifler bu tür bir yayılma için iyi bir şekilde uygun değildir; tek alternatifimiz, bir değeri tüm çağrı zinciri boyunca geçmek olacaktı.
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(#=...=#))
# ...
endIdioms
Unshare mutable state
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.
endScoped values as globals
Bir kapsamlı değerin değerine erişmek için, kapsamlı değerin kendisinin (leksikal) kapsamda olması gerekir. Bu, çoğu zaman kapsamlı değerleri sabit küresel değişkenler olarak kullanmak isteyeceğiniz anlamına gelir.
using Base.ScopedValues
const sval = ScopedValue(1)Gerçekten de kapsamlı değerleri gizli fonksiyon argümanları olarak düşünebiliriz.
Bu, onların küresel olmayanlar olarak kullanılmasını engellemez.
using Base.ScopedValues
import Base.Threads: @spawn
function main()
role = ScopedValue(:client)
function launch()
#...
role[]
end
@with role => :server @spawn launch()
launch()
endAma bu durumlarda işlev argümanını doğrudan geçmek daha basit olabilirdi.
Very many ScopedValues
Eğer bir modül için birçok ScopedValue oluşturuyorsanız, bunları tutmak için özel bir yapı kullanmak daha iyi olabilir.
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
endAPI docs
Base.ScopedValues.ScopedValue — TypeScopedValue(x)Dinamik kapsamlar arasında değerleri yaymak için bir konteyner oluşturun. Yeni bir dinamik kapsam oluşturmak ve girmek için with kullanın.
Değerler yalnızca yeni bir dinamik kapsam içine girerken ayarlanabilir ve referans verilen değer, bir dinamik kapsamın yürütülmesi sırasında sabit kalacaktır.
Dinamik kapsamlar görevler arasında yayılır.
Örnekler
julia> using Base.ScopedValues;
julia> const sval = ScopedValue(1);
julia> sval[]
1
julia> with(sval => 2) do
sval[]
end
2
julia> sval[]
1Scoped values, Julia 1.11'de tanıtılmıştır. Julia 1.8+ sürümünde uyumlu bir uygulama ScopedValues.jl paketinden mevcuttur.
Base.ScopedValues.with — Functionwith(f, (var::ScopedValue{T} => val)...)f'yi var'ı val olarak ayarlayarak yeni bir dinamik kapsamda çalıştırın. val, T türüne dönüştürülecektir.
Ayrıca bakınız: ScopedValues.@with, ScopedValues.ScopedValue, ScopedValues.get.
Örnekler
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)
12Base.ScopedValues.@with — Macro@with (var::ScopedValue{T} => val)... exprwith makrosu. @with var=>val expr ifadesi, var'ı val olarak ayarlayarak yeni bir dinamik kapsamda expr'i değerlendirir. val, T türüne dönüştürülecektir. @with var=>val expr, with(var=>val) do expr end ile eşdeğerdir, ancak @with bir kapanış oluşturmaktan kaçınır.
Ayrıca bakınız: ScopedValues.with, ScopedValues.ScopedValue, ScopedValues.get.
Örnekler
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
103Base.isassigned — Methodisassigned(val::ScopedValue)Bir ScopedValue'ın atanmış bir değere sahip olup olmadığını test eder.
Ayrıca bakınız: ScopedValues.with, ScopedValues.@with, ScopedValues.get.
Örnekler
julia> using Base.ScopedValues
julia> a = ScopedValue(1); b = ScopedValue{Int}();
julia> isassigned(a)
true
julia> isassigned(b)
falseBase.ScopedValues.get — Functionget(val::ScopedValue{T})::Union{Nothing, Some{T}}Eğer kapsamlı değer ayarlanmamışsa ve varsayılan bir değeri yoksa, nothing döndür. Aksi takdirde, mevcut değeri ile Some{T} döndürür.
Ayrıca bakınız: ScopedValues.with, ScopedValues.@with, ScopedValues.ScopedValue.
Örnekler
julia> using Base.ScopedValues
julia> a = ScopedValue(42); b = ScopedValue{Int}();
julia> ScopedValues.get(a)
Some(42)
julia> isnothing(ScopedValues.get(b))
trueImplementation notes and performance
Scope'lar kalıcı bir sözlük kullanır. Arama ve ekleme O(log(32, n))'dir, dinamik kapsam girişi sırasında küçük bir veri kopyalanır ve değişmeyen veriler diğer kapsamlar arasında paylaşılır.
Scope nesnesi kendisi kullanıcıya yönelik değildir ve gelecekteki bir Julia sürümünde değiştirilebilir.
Design inspiration
Bu tasarım, JEPS-429 tarafından büyük ölçüde ilham alınarak oluşturulmuştur; bu da birçok Lisp lehçesindeki dinamik olarak kapsamlı serbest değişkenlerden esinlenmiştir. Özellikle Interlisp-D ve onun derin bağlama stratejisi.
Önceki tartışılan tasarım, PEPS-567 gibi bağlam değişkenleri ve Julia'da ContextVariablesX.jl olarak uygulanmıştır.