Control Flow

تقدم جوليا مجموعة متنوعة من هياكل التحكم في التدفق:

آلية التحكم الخمسة الأولى هي قياسية للغات البرمجة عالية المستوى. 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 Exceptions

تُرمى Exceptions عندما يحدث شرط غير متوقع. تُدرج Exceptions المدمجة أدناه جميعها تعطل التدفق الطبيعي للتحكم.

Exception
ArgumentError
BoundsError
CompositeException
DimensionMismatch
DivideError
DomainError
EOFError
ErrorException
InexactError
InitError
InterruptException
InvalidStateException
KeyError
LoadError
OutOfMemoryError
ReadOnlyMemoryError
RemoteException
MethodError
OverflowError
Meta.ParseError
SystemError
TypeError
UndefRefError
UndefVarError
StringIndexError

على سبيل المثال، فإن دالة 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")
Note

عند كتابة رسالة خطأ، يُفضل أن تكون الكلمة الأولى بحروف صغيرة. على سبيل المثال،

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 باختبار 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

Julia 1.8

تتطلب هذه الوظيفة على الأقل جوليا 1.8.

في بعض الحالات، قد لا يرغب المرء فقط في التعامل مع حالة الخطأ بشكل مناسب، ولكن أيضًا في تشغيل بعض الشيفرات فقط إذا نجح كتلة try. لهذا، يمكن تحديد عبارة else بعد كتلة catch التي يتم تشغيلها كلما لم يتم طرح أي خطأ سابقًا. الميزة على تضمين هذه الشيفرة في كتلة try بدلاً من ذلك هي أن أي أخطاء إضافية لا يتم التقاطها بصمت بواسطة عبارة catch.

local x
try
    x = read("file", String)
catch
    # handle read errors
else
    # do something with x
end
Note

تقدم جمل 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.