Control Flow
تقدم جوليا مجموعة متنوعة من هياكل التحكم في التدفق:
- Compound Expressions:
ابدأو;. - Conditional Evaluation:
إذا-إذا كان-آخرو?:(المشغل الثلاثي). - Short-Circuit Evaluation: عوامل منطقية
&&(“و”) و||(“أو”)، وأيضًا المقارنات المتسلسلة. - Repeated Evaluation: Loops:
بينماولكل. - Exception Handling:
try-catch,errorوthrow. - Tasks (aka Coroutines):
yieldto.
آلية التحكم الخمسة الأولى هي قياسية للغات البرمجة عالية المستوى. Task ليست قياسية: فهي توفر تدفق تحكم غير محلي، مما يجعل من الممكن التبديل بين الحسابات المعلقة مؤقتًا. هذه بنية قوية: يتم تنفيذ كل من معالجة الاستثناءات والمهام المتعددة التعاونية في جوليا باستخدام المهام. تتطلب البرمجة اليومية عدم الاستخدام المباشر للمهام، ولكن يمكن حل بعض المشكلات بسهولة أكبر باستخدام المهام.
Compound Expressions
أحيانًا يكون من الملائم أن يكون لديك تعبير واحد يقوم بتقييم عدة تعبيرات فرعية بالتتابع، مع إرجاع قيمة التعبير الفرعي الأخير كقيمته. هناك نوعان من البنى في جوليا تحقق ذلك: كتل begin وسلاسل ;. قيمة كلا البنيتين المركبتين هي قيمة التعبير الفرعي الأخير. إليك مثال على كتلة begin:
julia> z = begin
x = 1
y = 2
x + y
end
3نظرًا لأن هذه تعبيرات صغيرة وبسيطة، يمكن وضعها بسهولة في سطر واحد، وهنا يأتي دور بناء جملة سلسلة ; المفيد:
julia> z = (x = 1; y = 2; x + y)
3هذه الصيغة مفيدة بشكل خاص مع شكل تعريف الدالة الموجز في سطر واحد الذي تم تقديمه في Functions. على الرغم من أنه من الشائع، لا يوجد شرط بأن تكون كتل begin متعددة الأسطر أو أن تكون سلاسل ; في سطر واحد:
julia> begin x = 1; y = 2; x + y end
3
julia> (x = 1;
y = 2;
x + y)
3Conditional Evaluation
تسمح التقييمات الشرطية بتقييم أجزاء من الشيفرة أو عدم تقييمها اعتمادًا على قيمة تعبير بولي. إليك بنية بناء جملة الشرط if-elseif-else:
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
endإذا كانت تعبير الشرط x < y هو true، فإن الكتلة المقابلة يتم تقييمها؛ وإلا يتم تقييم تعبير الشرط x > y، وإذا كان true، يتم تقييم الكتلة المقابلة؛ إذا لم يكن أي من التعبيرين صحيحًا، يتم تقييم كتلة else. هنا هو في العمل:
julia> function test(x, y)
if x < y
println("x is less than y")
elseif x > y
println("x is greater than y")
else
println("x is equal to y")
end
end
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to yكتل elseif و else اختيارية، ويمكن استخدام العديد من كتل elseif حسب الرغبة. يتم تقييم تعبيرات الشرط في بناء if-elseif-else حتى يتم تقييم الأول منها إلى true، بعد ذلك يتم تقييم الكتلة المرتبطة، ولا يتم تقييم أي تعبيرات أو كتل شرطية أخرى.
if الكتل "متسربة"، أي أنها لا تقدم نطاقًا محليًا. هذا يعني أن المتغيرات الجديدة المعرفة داخل عبارات if يمكن استخدامها بعد كتلة if، حتى لو لم يتم تعريفها من قبل. لذا، يمكننا أن نكون قد عرفنا دالة test أعلاه كـ
julia> function test(x,y)
if x < y
relation = "less than"
elseif x == y
relation = "equal to"
else
relation = "greater than"
end
println("x is ", relation, " y.")
end
test (generic function with 1 method)
julia> test(2, 1)
x is greater than y.تم إعلان المتغير relation داخل كتلة if، ولكن تم استخدامه خارجها. ومع ذلك، عند الاعتماد على هذا السلوك، تأكد من أن جميع مسارات الشيفرة الممكنة تعرف قيمة للمتغير. التغيير التالي للدالة أعلاه يؤدي إلى خطأ في وقت التشغيل
julia> function test(x,y)
if x < y
relation = "less than"
elseif x == y
relation = "equal to"
end
println("x is ", relation, " y.")
end
test (generic function with 1 method)
julia> test(1,2)
x is less than y.
julia> test(2,1)
ERROR: UndefVarError: `relation` not defined in local scope
Stacktrace:
[1] test(::Int64, ::Int64) at ./none:7if الكتل أيضًا تعيد قيمة، والتي قد تبدو غير بديهية للمستخدمين القادمين من العديد من اللغات الأخرى. هذه القيمة هي ببساطة قيمة الإرجاع لآخر عبارة تم تنفيذها في الفرع الذي تم اختياره، لذا
julia> x = 3
3
julia> if x > 0
"positive!"
else
"negative..."
end
"positive!"لاحظ أن العبارات الشرطية القصيرة جدًا (سطر واحد) تُعبر غالبًا باستخدام تقييم الدوائر القصيرة في جوليا، كما هو موضح في القسم التالي.
على عكس C و MATLAB و Perl و Python و Ruby - ولكن مثل Java وبعض اللغات الأخرى الأكثر صرامة والمُعرفة - فإنه يُعتبر خطأ إذا كانت قيمة تعبير الشرط أي شيء غير true أو false:
julia> if 1
println("true")
end
ERROR: TypeError: non-boolean (Int64) used in boolean contextتشير هذه الرسالة إلى أن الشرط كان من النوع الخاطئ: Int64 بدلاً من النوع المطلوب Bool.
يُعرف "المشغل الثلاثي" ?: بأنه مرتبط ارتباطًا وثيقًا بصيغة if-elseif-else، ولكنه يُستخدم حيثما تكون هناك حاجة لاختيار شرطي بين قيم تعبيرية فردية، على عكس التنفيذ الشرطي لكتل أطول من الشيفرة. ويحصل على اسمه كونه المشغل الوحيد في معظم اللغات الذي يأخذ ثلاثة معاملات:
a ? b : cالتعبير a، قبل ؟، هو تعبير شرط، وتقوم العملية الثلاثية بتقييم التعبير b، قبل :، إذا كانت الحالة a صحيحة أو التعبير c، بعد :، إذا كانت خاطئة. لاحظ أن المسافات حول ؟ و : إلزامية: تعبير مثل a?b:c ليس تعبيرًا ثلاثيًا صالحًا (لكن سطر جديد مقبول بعد كل من ؟ و :).
أسهل طريقة لفهم هذا السلوك هي رؤية مثال. في المثال السابق، يتم مشاركة استدعاء println من قبل جميع الفروع الثلاثة: الخيار الحقيقي الوحيد هو أي سلسلة حرفية لطباعة. يمكن كتابة هذا بشكل أكثر اختصارًا باستخدام عامل التشغيل الثلاثي. من أجل الوضوح، دعنا نجرب نسخة ذات اتجاهين أولاً:
julia> x = 1; y = 2;
julia> println(x < y ? "less than" : "not less than")
less than
julia> x = 1; y = 0;
julia> println(x < y ? "less than" : "not less than")
not less thanإذا كانت العبارة x < y صحيحة، فإن تعبير المشغل الثلاثي بالكامل يقيم إلى السلسلة "أقل من" وإلا فإنه يقيم إلى السلسلة "ليس أقل من". يتطلب المثال الأصلي ثلاثي الطرق ربط استخدامات متعددة من المشغل الثلاثي معًا:
julia> test(x, y) = println(x < y ? "x is less than y" :
x > y ? "x is greater than y" : "x is equal to y")
test (generic function with 1 method)
julia> test(1, 2)
x is less than y
julia> test(2, 1)
x is greater than y
julia> test(1, 1)
x is equal to yلتسهيل الربط، يرتبط العامل من اليمين إلى اليسار.
من المهم أنه مثل if-elseif-else، يتم تقييم التعبيرات قبل وبعد : فقط إذا كانت تعبير الشرط يقيم إلى true أو false، على التوالي:
julia> v(x) = (println(x); x)
v (generic function with 1 method)
julia> 1 < 2 ? v("yes") : v("no")
yes
"yes"
julia> 1 > 2 ? v("yes") : v("no")
no
"no"Short-Circuit Evaluation
تتوافق عوامل التشغيل && و || في جوليا مع عمليات "و" و "أو" المنطقية، على التوالي، وعادة ما تُستخدم لهذا الغرض. ومع ذلك، لديهم خاصية إضافية تتمثل في التقييم القصير: فهم لا يقيمون بالضرورة حجتهم الثانية، كما هو موضح أدناه. (هناك أيضًا عوامل تشغيل بتية & و | يمكن استخدامها كـ "و" و "أو" منطقية بدون سلوك التقييم القصير، ولكن احذر من أن & و | لهما أسبقية أعلى من && و || من حيث ترتيب التقييم.)
تقييم الدائرة القصيرة مشابه جدًا لتقييم الشرط. يتم العثور على هذا السلوك في معظم لغات البرمجة الإجرائية التي تحتوي على عوامل التشغيل المنطقية && و ||: في سلسلة من التعبيرات المنطقية المتصلة بهذه العوامل، يتم تقييم الحد الأدنى من التعبيرات اللازمة لتحديد القيمة المنطقية النهائية لسلسلة التعبيرات بأكملها. تشير بعض اللغات (مثل بايثون) إلى هذه العوامل باسم and (&&) و or (||). بشكل صريح، يعني هذا أن:
- في التعبير
a && b، يتم تقييم التعبير الفرعيbفقط إذا كانتaتقيم إلىtrue. - في التعبير
a || b، يتم تقييم التعبير الفرعيbفقط إذا كانت قيمةaتساويfalse.
السبب هو أن a && b يجب أن تكون false إذا كانت a false، بغض النظر عن قيمة b، وبالمثل، يجب أن تكون قيمة a || b صحيحة إذا كانت a صحيحة، بغض النظر عن قيمة b. كل من && و || تتجمع إلى اليمين، لكن && لها أولوية أعلى من ||. من السهل تجربة هذا السلوك:
julia> t(x) = (println(x); true)
t (generic function with 1 method)
julia> f(x) = (println(x); false)
f (generic function with 1 method)
julia> t(1) && t(2)
1
2
true
julia> t(1) && f(2)
1
2
false
julia> f(1) && t(2)
1
false
julia> f(1) && f(2)
1
false
julia> t(1) || t(2)
1
true
julia> t(1) || f(2)
1
true
julia> f(1) || t(2)
1
2
true
julia> f(1) || f(2)
1
2
falseيمكنك بسهولة التجربة بنفس الطريقة مع التجميع والأسبقية لمجموعات مختلفة من عوامل التشغيل && و ||.
هذا السلوك يُستخدم بشكل متكرر في جوليا لتشكيل بديل لبيانات if القصيرة جدًا. بدلاً من if <cond> <statement> end، يمكن كتابة <cond> && <statement> (التي يمكن قراءتها على أنها: <cond> ثم <statement>). وبالمثل، بدلاً من if ! <cond> <statement> end، يمكن كتابة <cond> || <statement> (التي يمكن قراءتها على أنها: <cond> أو <statement>).
على سبيل المثال، يمكن تعريف روتين العامل التكراري على النحو التالي:
julia> function fact(n::Int)
n >= 0 || error("n must be non-negative")
n == 0 && return 1
n * fact(n-1)
end
fact (generic function with 1 method)
julia> fact(5)
120
julia> fact(0)
1
julia> fact(-1)
ERROR: n must be non-negative
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] fact(::Int64) at ./none:2
[3] top-level scopeيمكن إجراء العمليات المنطقية بدون تقييم قصير الدائرة باستخدام عوامل التشغيل المنطقية البتية التي تم تقديمها في Mathematical Operations and Elementary Functions: & و |. هذه وظائف عادية، والتي تدعم عن طريق الصدفة بناء جملة عامل التشغيل في الوسط، لكنها دائمًا تقيم حججها:
julia> f(1) & t(2)
1
2
false
julia> t(1) | t(2)
1
2
trueتمامًا مثل تعبيرات الشرط المستخدمة في if و elseif أو عامل التشغيل الثلاثي، يجب أن تكون المعاملات لـ && أو || قيمًا منطقية (true أو false). استخدام قيمة غير منطقية في أي مكان باستثناء الإدخال الأخير في سلسلة شرطية هو خطأ:
julia> 1 && true
ERROR: TypeError: non-boolean (Int64) used in boolean contextمن ناحية أخرى، يمكن استخدام أي نوع من التعبيرات في نهاية سلسلة الشرط. سيتم تقييمه وإرجاعه اعتمادًا على الشروط السابقة:
julia> true && (x = (1, 2, 3))
(1, 2, 3)
julia> false && (x = (1, 2, 3))
falseRepeated Evaluation: Loops
هناك نوعان من البنى لتقييم التعبيرات بشكل متكرر: حلقة while وحلقة for. إليك مثال على حلقة while:
julia> i = 1;
julia> while i <= 3
println(i)
global i += 1
end
1
2
3تقوم حلقة while بتقييم تعبير الشرط (i <= 3 في هذه الحالة)، وطالما أنه يبقى true، تستمر أيضًا في تقييم جسم حلقة while. إذا كان تعبير الشرط false عند الوصول إلى حلقة while لأول مرة، فلن يتم تقييم الجسم أبدًا.
تجعل حلقة for كتابة العبارات المتكررة الشائعة أسهل. نظرًا لأن العد لأعلى ولأسفل كما تفعل حلقة while أعلاه أمر شائع جدًا، يمكن التعبير عنه بشكل أكثر إيجازًا باستخدام حلقة for:
julia> for i = 1:3
println(i)
end
1
2
3هنا 1:3 هو كائن range، يمثل تسلسل الأرقام 1، 2، 3. حلقة for تتكرر عبر هذه القيم، مع تعيين كل واحدة منها بالتتابع إلى المتغير i. بشكل عام، يمكن أن تتكرر بنية for عبر أي كائن "قابل للتكرار" (أو "حاوية")، من نطاق مثل 1:3 أو 1:3:13 (وهو StepRange الذي يشير إلى كل عدد ثالث 1، 4، 7، …، 13) إلى حاويات أكثر عمومية مثل المصفوفات، بما في ذلك iterators defined by user code أو الحزم الخارجية. بالنسبة للحاويات الأخرى غير النطاقات، يتم استخدام الكلمة البديلة (لكنها متكافئة تمامًا) in أو ∈ عادةً بدلاً من =، لأنها تجعل الكود أكثر وضوحًا:
julia> for i in [1,4,0]
println(i)
end
1
4
0
julia> for s ∈ ["foo","bar","baz"]
println(s)
end
foo
bar
bazسيتم تقديم ومناقشة أنواع مختلفة من الحاويات القابلة للتكرار في الأقسام اللاحقة من الدليل (انظر، على سبيل المثال، Multi-dimensional Arrays).
واحدة من الفروقات المهمة بين شكل حلقة while السابقة وشكل حلقة for هي النطاق الذي تكون فيه المتغيرات مرئية. حلقة for دائمًا ما تقدم متغير تكرار جديد في جسمها، بغض النظر عما إذا كان هناك متغير بنفس الاسم موجود في النطاق المحيط. هذا يعني أنه من ناحية، لا حاجة لإعلان i قبل الحلقة. من ناحية أخرى، لن تكون مرئية خارج الحلقة، ولن يتأثر متغير خارجي بنفس الاسم. ستحتاج إما إلى جلسة تفاعلية جديدة أو اسم متغير مختلف لاختبار ذلك:
julia> for j = 1:3
println(j)
end
1
2
3
julia> j
ERROR: UndefVarError: `j` not defined in `Main`julia> j = 0;
julia> for j = 1:3
println(j)
end
1
2
3
julia> j
0استخدم for outer لتعديل السلوك الأخير وإعادة استخدام متغير محلي موجود.
انظر Scope of Variables للحصول على شرح مفصل لنطاق المتغيرات، outer، وكيف يعمل في جوليا.
من الملائم أحيانًا إنهاء تكرار while قبل أن يتم التحقق من أن شرط الاختبار غير صحيح أو التوقف عن التكرار في حلقة for قبل الوصول إلى نهاية كائن التكرار. يمكن تحقيق ذلك باستخدام الكلمة الرئيسية break:
julia> i = 1;
julia> while true
println(i)
if i >= 3
break
end
global i += 1
end
1
2
3
julia> for j = 1:1000
println(j)
if j >= 3
break
end
end
1
2
3بدون كلمة break، فإن حلقة while أعلاه لن تنتهي من تلقاء نفسها، وستستمر حلقة for حتى 1000. يتم الخروج من هاتين الحلقتين مبكرًا باستخدام break.
في ظروف أخرى، من المفيد أن تكون قادرًا على إيقاف تكرار والانتقال إلى التالي على الفور. يحقق الكلمة الرئيسية continue ذلك:
julia> for i = 1:10
if i % 3 != 0
continue
end
println(i)
end
3
6
9هذا مثال مصطنع إلى حد ما لأنه يمكننا إنتاج نفس السلوك بشكل أوضح من خلال نفي الشرط ووضع استدعاء println داخل كتلة if. في الاستخدام الواقعي، هناك المزيد من التعليمات البرمجية التي يجب تقييمها بعد continue، وغالبًا ما تكون هناك نقاط متعددة يتم منها استدعاء continue.
يمكن دمج حلقات for المتداخلة المتعددة في حلقة خارجية واحدة، مما يشكل المنتج الديكارتي لعناصرها:
julia> for i = 1:2, j = 3:4
println((i, j))
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)مع هذه الصياغة، يمكن أن تشير القوائم القابلة للتكرار إلى متغيرات الحلقة الخارجية؛ على سبيل المثال، for i = 1:n, j = 1:i هو أمر صالح. ومع ذلك، فإن عبارة break داخل مثل هذه الحلقة تخرج من كل مجموعة الحلقات، وليس فقط من الحلقة الداخلية. يتم تعيين كلا المتغيرين (i و j) إلى قيم تكرارهما الحالية في كل مرة تعمل فيها الحلقة الداخلية. لذلك، فإن التعيينات لـ i لن تكون مرئية للتكرارات اللاحقة:
julia> for i = 1:2, j = 3:4
println((i, j))
i = 0
end
(1, 3)
(1, 4)
(2, 3)
(2, 4)إذا تم إعادة كتابة هذا المثال لاستخدام كلمة for لكل متغير، فسيكون الناتج مختلفًا: ستحتوي القيمتين الثانية والرابعة على 0.
يمكن تكرار عدة حاويات في نفس الوقت في حلقة for واحدة باستخدام zip:
julia> for (j, k) in zip([1 2 3], [4 5 6 7])
println((j,k))
end
(1, 4)
(2, 5)
(3, 6)باستخدام zip سيتم إنشاء مُكرّر يكون عبارة عن مجموعة تحتوي على المُكرّرات الفرعية للحاويات المرسلة إليه. سيقوم مُكرّر zip بالتكرار على جميع المُكرّرات الفرعية بالترتيب، مختارًا العنصر $i$ من كل مُكرّر فرعي في التكرار $i$ من حلقة for. بمجرد أن تنفد أي من المُكرّرات الفرعية، ستتوقف حلقة for.
Exception Handling
عندما يحدث شرط غير متوقع، قد تكون الدالة غير قادرة على إرجاع قيمة معقولة للمتصل بها. في مثل هذه الحالات، قد يكون من الأفضل أن يؤدي الشرط الاستثنائي إلى إنهاء البرنامج مع طباعة رسالة خطأ تشخيصية، أو إذا كان المبرمج قد قدم كودًا للتعامل مع مثل هذه الظروف الاستثنائية، فيمكن السماح لذلك الكود باتخاذ الإجراء المناسب.
Built-in Exceptions
تُرمى Exceptions عندما يحدث شرط غير متوقع. تُدرج Exceptions المدمجة أدناه جميعها تعطل التدفق الطبيعي للتحكم.
على سبيل المثال، فإن دالة sqrt ترمي DomainError إذا تم تطبيقها على قيمة حقيقية سالبة:
julia> sqrt(-1)
ERROR: DomainError with -1.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]يمكنك تعريف استثناءاتك الخاصة بالطريقة التالية:
julia> struct MyCustomException <: Exception endThe throw function
يمكن إنشاء الاستثناءات بشكل صريح باستخدام throw. على سبيل المثال، يمكن كتابة دالة معرفة فقط للأعداد غير السلبية إلى 4d61726b646f776e2e436f64652822222c20227468726f772229_40726566 كـ DomainError إذا كانت الحجة سالبة:
julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be non-negative"))
f (generic function with 1 method)
julia> f(1)
0.36787944117144233
julia> f(-1)
ERROR: DomainError with -1:
argument must be non-negative
Stacktrace:
[1] f(::Int64) at ./none:1لاحظ أن DomainError بدون أقواس ليست استثناءً، بل نوع من الاستثناء. يحتاج إلى أن يتم استدعاؤه للحصول على كائن Exception:
julia> typeof(DomainError(nothing)) <: Exception
true
julia> typeof(DomainError) <: Exception
falseبالإضافة إلى ذلك، تأخذ بعض أنواع الاستثناءات واحدًا أو أكثر من المعاملات التي تُستخدم للإبلاغ عن الأخطاء:
julia> throw(UndefVarError(:x))
ERROR: UndefVarError: `x` not definedيمكن تنفيذ هذه الآلية بسهولة من خلال أنواع الاستثناءات المخصصة باتباع الطريقة التي كُتبت بها UndefVarError :
julia> struct MyUndefVarError <: Exception
var::Symbol
end
julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " not defined")عند كتابة رسالة خطأ، يُفضل أن تكون الكلمة الأولى بحروف صغيرة. على سبيل المثال،
size(A) == size(B) || throw(DimensionMismatch("حجم A لا يساوي حجم B"))
يفضل على
size(A) == size(B) || throw(DimensionMismatch("حجم A لا يساوي حجم B")).
ومع ذلك، في بعض الأحيان يكون من المنطقي الاحتفاظ بالحرف الأول الكبير، على سبيل المثال إذا كان أحد المعاملات في دالة هو حرف كبير:
size(A,1) == size(B,2) || throw(DimensionMismatch("A has first dimension...")).
Errors
تُستخدم دالة error لإنتاج ErrorException التي تعطل التدفق الطبيعي للتحكم.
افترض أننا نريد إيقاف التنفيذ على الفور إذا تم أخذ الجذر التربيعي لعدد سالب. للقيام بذلك، يمكننا تعريف نسخة غير دقيقة من دالة sqrt التي تثير خطأ إذا كانت حجتها سالبة:
julia> fussy_sqrt(x) = x >= 0 ? sqrt(x) : error("negative x not allowed")
fussy_sqrt (generic function with 1 method)
julia> fussy_sqrt(2)
1.4142135623730951
julia> fussy_sqrt(-1)
ERROR: negative x not allowed
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] fussy_sqrt(::Int64) at ./none:1
[3] top-level scopeإذا تم استدعاء fussy_sqrt بقيمة سالبة من دالة أخرى، بدلاً من محاولة متابعة تنفيذ الدالة المستدعية، فإنه يعود على الفور، مع عرض رسالة الخطأ في الجلسة التفاعلية:
julia> function verbose_fussy_sqrt(x)
println("before fussy_sqrt")
r = fussy_sqrt(x)
println("after fussy_sqrt")
return r
end
verbose_fussy_sqrt (generic function with 1 method)
julia> verbose_fussy_sqrt(2)
before fussy_sqrt
after fussy_sqrt
1.4142135623730951
julia> verbose_fussy_sqrt(-1)
before fussy_sqrt
ERROR: negative x not allowed
Stacktrace:
[1] error at ./error.jl:33 [inlined]
[2] fussy_sqrt at ./none:1 [inlined]
[3] verbose_fussy_sqrt(::Int64) at ./none:3
[4] top-level scopeThe try/catch statement
تسمح عبارة try/catch باختبار Exceptions، وللتعامل بشكل سلس مع الأمور التي قد تكسر تطبيقك عادةً. على سبيل المثال، في الكود أدناه، ستقوم الدالة لحساب الجذر التربيعي عادةً بإلقاء استثناء. من خلال وضع كتلة try/catch حولها، يمكننا التخفيف من ذلك هنا. يمكنك اختيار كيفية التعامل مع هذا الاستثناء، سواء بتسجيله، أو إرجاع قيمة بديلة، أو كما في الحالة أدناه حيث قمنا فقط بطباعة بيان. شيء واحد يجب التفكير فيه عند اتخاذ قرار بشأن كيفية التعامل مع المواقف غير المتوقعة هو أن استخدام كتلة try/catch أبطأ بكثير من استخدام التفرع الشرطي للتعامل مع تلك المواقف. أدناه هناك المزيد من الأمثلة على التعامل مع الاستثناءات باستخدام كتلة try/catch:
julia> try
sqrt("ten")
catch e
println("You should have entered a numeric value")
end
You should have entered a numeric valueتسمح عبارات try/catch أيضًا بحفظ Exception في متغير. المثال المصطنع التالي يحسب الجذر التربيعي للعنصر الثاني من x إذا كان x قابلًا للفهرسة، وإلا يفترض أن x هو عدد حقيقي ويعيد جذرها التربيعي:
julia> sqrt_second(x) = try
sqrt(x[2])
catch y
if isa(y, DomainError)
sqrt(complex(x[2], 0))
elseif isa(y, BoundsError)
sqrt(x)
end
end
sqrt_second (generic function with 1 method)
julia> sqrt_second([1 4])
2.0
julia> sqrt_second([1 -4])
0.0 + 2.0im
julia> sqrt_second(9)
3.0
julia> sqrt_second(-9)
ERROR: DomainError with -9.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]لاحظ أن الرمز الذي يلي catch سيتم تفسيره دائمًا كاسم للاستثناء، لذا يجب توخي الحذر عند كتابة تعبيرات try/catch في سطر واحد. الكود التالي لن يعمل لإرجاع قيمة x في حالة حدوث خطأ:
try bad() catch x endبدلاً من ذلك، استخدم فاصلة منقوطة أو أدخل فاصل سطر بعد catch:
try bad() catch; x end
try bad()
catch
x
endقوة بناء try/catch تكمن في القدرة على فك تجميع حساب متداخل بعمق على الفور إلى مستوى أعلى بكثير في مكدس الدوال المستدعاة. هناك حالات لم يحدث فيها خطأ، ولكن القدرة على فك المكدس وتمرير قيمة إلى مستوى أعلى مرغوبة. توفر جوليا الدوال rethrow، backtrace، catch_backtrace و current_exceptions لمعالجة الأخطاء بشكل أكثر تقدمًا.
else Clauses
تتطلب هذه الوظيفة على الأقل جوليا 1.8.
في بعض الحالات، قد لا يرغب المرء فقط في التعامل مع حالة الخطأ بشكل مناسب، ولكن أيضًا في تشغيل بعض الشيفرات فقط إذا نجح كتلة try. لهذا، يمكن تحديد عبارة else بعد كتلة catch التي يتم تشغيلها كلما لم يتم طرح أي خطأ سابقًا. الميزة على تضمين هذه الشيفرة في كتلة try بدلاً من ذلك هي أن أي أخطاء إضافية لا يتم التقاطها بصمت بواسطة عبارة catch.
local x
try
x = read("file", String)
catch
# handle read errors
else
# do something with x
endتقدم جمل try و catch و else و finally كل منها كتل نطاق خاصة بها، لذا إذا تم تعريف متغير فقط في كتلة try، فلا يمكن الوصول إليه من قبل جملة else أو finally:
julia> try
foo = 1
catch
else
foo
end
ERROR: UndefVarError: `foo` not defined in `Main`
Suggestion: check for spelling errors or missing imports.استخدم local keyword خارج كتلة try لجعل المتغير متاحًا من أي مكان ضمن النطاق الخارجي.
finally Clauses
في الشيفرة التي تقوم بإجراء تغييرات على الحالة أو تستخدم موارد مثل الملفات، هناك عادةً أعمال تنظيف (مثل إغلاق الملفات) تحتاج إلى القيام بها عند الانتهاء من الشيفرة. يمكن أن تعقد الاستثناءات هذه المهمة، حيث يمكن أن تتسبب في خروج كتلة من الشيفرة قبل الوصول إلى نهايتها الطبيعية. يوفر الكلمة الرئيسية finally وسيلة لتشغيل بعض الشيفرة عندما تخرج كتلة معينة من الشيفرة، بغض النظر عن كيفية خروجها.
على سبيل المثال، إليك كيف يمكننا ضمان أن الملف المفتوح مغلق:
f = open("file")
try
# operate on file f
finally
close(f)
endعندما تترك السيطرة كتلة try (على سبيل المثال بسبب return، أو الانتهاء بشكل طبيعي)، سيتم تنفيذ close(f). إذا خرجت كتلة try بسبب استثناء، فسيستمر الاستثناء في الانتشار. يمكن دمج كتلة catch مع try و finally أيضًا. في هذه الحالة، ستعمل كتلة finally بعد أن تتعامل كتلة catch مع الخطأ.
Tasks (aka Coroutines)
المهام هي ميزة في تدفق التحكم تسمح بتعليق العمليات واستئنافها بطريقة مرنة. نذكرها هنا فقط من أجل الاكتمال؛ لمناقشة كاملة، انظر Asynchronous Programming.