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)
3
Conditional 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:7
if
الكتل أيضًا تعيد قيمة، والتي قد تبدو غير بديهية للمستخدمين القادمين من العديد من اللغات الأخرى. هذه القيمة هي ببساطة قيمة الإرجاع لآخر عبارة تم تنفيذها في الفرع الذي تم اختياره، لذا
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))
false
Repeated 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 Exception
s
تُرمى Exception
s عندما يحدث شرط غير متوقع. تُدرج Exception
s المدمجة أدناه جميعها تعطل التدفق الطبيعي للتحكم.
على سبيل المثال، فإن دالة 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 end
The 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 scope
The try/catch
statement
تسمح عبارة try/catch
باختبار Exception
s، وللتعامل بشكل سلس مع الأمور التي قد تكسر تطبيقك عادةً. على سبيل المثال، في الكود أدناه، ستقوم الدالة لحساب الجذر التربيعي عادةً بإلقاء استثناء. من خلال وضع كتلة 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.