Scoped Values
توفر القيم المحددة تنفيذًا للتوجيه الديناميكي في جوليا.
Lexical scoping هو السلوك الافتراضي في جوليا. تحت النطاق المعجمي، يتم تحديد نطاق المتغير من خلال الهيكل المعجمي (النصي) للبرنامج. تحت النطاق الديناميكي، يتم ربط المتغير بأحدث قيمة تم تعيينها خلال تنفيذ البرنامج.
حالة القيمة المحددة تعتمد على مسار تنفيذ البرنامج. هذا يعني أنه بالنسبة لقيمة محددة قد تلاحظ قيمًا مختلفة متعددة في نفس الوقت.
تم تقديم القيم المحددة في جوليا 1.11. في جوليا 1.8+ ، تتوفر تنفيذ متوافق من الحزمة ScopedValues.jl.
في أبسط صوره يمكنك إنشاء ScopedValue
مع قيمة افتراضية ثم استخدام with
أو @with
للدخول في نطاق ديناميكي جديد. سيت inherit النطاق الجديد جميع القيم من النطاق الأب (وبشكل متكرر من جميع النطاقات الخارجية) مع أخذ القيمة المحددة في النطاق الأولوية على التعريفات السابقة.
لننظر أولاً إلى مثال على النطاق المعجمي. تبدأ عبارة let
نطاقًا معجميًا جديدًا يتم فيه حجب التعريف الخارجي لـ x
بواسطة تعريفه الداخلي.
x = 1
let x = 5
@show x # 5
end
@show x # 1
في المثال التالي، نظرًا لأن جوليا تستخدم النطاق المعجمي، فإن المتغير x
في جسم f
يشير إلى x
المحدد في النطاق العالمي، ودخول نطاق let
لا يغير القيمة التي تلاحظها f
.
x = 1
f() = @show x
let x = 5
f() # 1
end
f() # 1
الآن باستخدام ScopedValue
يمكننا استخدام التحديد الديناميكي.
using Base.ScopedValues
x = ScopedValue(1)
f() = @show x[]
with(x=>5) do
f() # 5
end
f() # 1
لاحظ أن القيمة الملاحظة لـ ScopedValue
تعتمد على مسار تنفيذ البرنامج.
من المنطقي غالبًا استخدام متغير const
للإشارة إلى قيمة محلية، ويمكنك تعيين قيمة لعدة ScopedValue
s من خلال استدعاء واحد لـ with
.
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
ScopedValues
يوفر نسخة ماكرو من with
. التعبير @with var=>val expr
يقوم بتقييم expr
في نطاق ديناميكي جديد مع تعيين var
إلى val
. @with var=>val expr
يعادل with(var=>val) do expr end
. ومع ذلك، يتطلب with
إغلاقًا أو دالة بدون وسائط، مما يؤدي إلى إطار استدعاء إضافي. كمثال، اعتبر الدالة التالية f
:
using Base.ScopedValues
const a = ScopedValue(1)
f(x) = a[] + x
إذا كنت ترغب في تشغيل f
في نطاق ديناميكي مع تعيين a
إلى 2
، يمكنك استخدام with
:
with(() -> f(10), a=>2)
ومع ذلك، يتطلب ذلك لف f
في دالة بدون وسائط. إذا كنت ترغب في تجنب إطار الاستدعاء الإضافي، يمكنك استخدام ماكرو @with
:
@with a=>2 f(10)
تُورَث النطاقات الديناميكية بواسطة Task
، في لحظة إنشاء المهمة. النطاقات الديناميكية لا تُنقل عبر عمليات Distributed.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
Example
في المثال أدناه نستخدم قيمة محددة لتنفيذ فحص الأذونات في تطبيق ويب. بعد تحديد أذونات الطلب، يتم الدخول إلى نطاق ديناميكي جديد ويتم تعيين القيمة المحددة LEVEL
. يمكن لأجزاء أخرى من التطبيق استعلام القيمة المحددة وستتلقى القيمة المناسبة. البدائل الأخرى مثل التخزين المحلي للمهام والمتغيرات العالمية ليست مناسبة جيدًا لهذا النوع من الانتشار؛ البديل الوحيد لدينا كان سيكون تمرير قيمة عبر سلسلة الاستدعاءات بأكملها.
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
Idioms
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.
end
Scoped values as globals
لكي تتمكن من الوصول إلى قيمة قيمة محددة، يجب أن تكون القيمة المحددة نفسها في (نطاق) النطاق. وهذا يعني أنه في معظم الأحيان، من المحتمل أن ترغب في استخدام القيم المحددة كقيم عالمية ثابتة.
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
لكن قد يكون من الأسهل ببساطة تمرير وسيط الدالة مباشرة في هذه الحالات.
Very many ScopedValues
إذا وجدت نفسك تقوم بإنشاء العديد من 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 docs
Base.ScopedValues.ScopedValue
— TypeScopedValue(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
تم تقديم القيم النطاقية في Julia 1.11. في Julia 1.8+، تتوفر تنفيذ متوافق من الحزمة ScopedValues.jl.
Base.ScopedValues.with
— Functionwith(f, (var::ScopedValue{T} => val)...)
نفذ f
في نطاق ديناميكي جديد مع تعيين var
إلى val
. سيتم تحويل val
إلى النوع T
.
انظر أيضًا: ScopedValues.@with
، ScopedValues.ScopedValue
، ScopedValues.get
.
أمثلة
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
Base.ScopedValues.@with
— Macro@with (var::ScopedValue{T} => val)... expr
نسخة الماكرو من with
. التعبير @with var=>val expr
يقوم بتقييم expr
في نطاق ديناميكي جديد مع تعيين var
إلى val
. سيتم تحويل val
إلى النوع T
. @with var=>val expr
يعادل with(var=>val) do expr end
، لكن @with
يتجنب إنشاء إغلاق.
انظر أيضًا: ScopedValues.with
، ScopedValues.ScopedValue
، ScopedValues.get
.
أمثلة
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
Base.isassigned
— Methodisassigned(val::ScopedValue)
اختبر ما إذا كان ScopedValue
يحتوي على قيمة معينة.
انظر أيضًا: ScopedValues.with
, ScopedValues.@with
, ScopedValues.get
.
أمثلة
julia> using Base.ScopedValues
julia> a = ScopedValue(1); b = ScopedValue{Int}();
julia> isassigned(a)
true
julia> isassigned(b)
false
Base.ScopedValues.get
— Functionget(val::ScopedValue{T})::Union{Nothing, Some{T}}
إذا لم يتم تعيين القيمة المحددة ولا تحتوي على قيمة افتراضية، ارجع nothing
. خلاف ذلك، ارجع Some{T}
مع القيمة الحالية.
انظر أيضًا: ScopedValues.with
، ScopedValues.@with
، ScopedValues.ScopedValue
.
أمثلة
julia> using Base.ScopedValues
julia> a = ScopedValue(42); b = ScopedValue{Int}();
julia> ScopedValues.get(a)
Some(42)
julia> isnothing(ScopedValues.get(b))
true
Implementation notes and performance
تستخدم Scope
قاموسًا دائمًا. البحث والإدراج هما O(log(32, n))
، عند دخول النطاق الديناميكي يتم نسخ كمية صغيرة من البيانات ويتم مشاركة البيانات غير المتغيرة بين النطاقات الأخرى.
كائن Scope
نفسه ليس موجهًا للمستخدم وقد يتغير في إصدار مستقبلي من جوليا.
Design inspiration
تم استلهام هذا التصميم بشكل كبير من JEPS-429، الذي استلهم بدوره من المتغيرات الحرة ذات النطاق الديناميكي في العديد من لهجات Lisp. بشكل خاص Interlisp-D واستراتيجية الربط العميق الخاصة به.
تم مناقشة تصميم سابق كان يتضمن متغيرات السياق على شكل PEPS-567 وتم تنفيذه في جوليا كـ ContextVariablesX.jl.