أوامر الرجوع (Returns) والقفز (Jumps)

من موسوعة حسوب

تعابير القفز

تمتاز لغة Kotlin بوجود ثلاثة تعابير (expressions) لنقل سياق البرنامج وهي:

  • return: للرجوع من أقرب دالةٍ محيطةٍ (enclosing) أو دالةٍ مجهولةٍ (anonymous function).
  • break : تنهي أقرب حلقةٍ محيطة.
  • continue : تستمر بالخطوة التالية لأقرب حلقةٍ محيطة.

إذ يُسمَح باستخدام أيّ من التعابير السابقة كجزءٍ من تعبيرٍ أكبر، مثل:

val s = person.name ?: return

ونوع هذه التعابير هو Nothing type.

تسمية الأوامر Break و Continue

يٌتاح في لغة Kotlin تحديد تسميةٍ (label) لأيّ تعبير، إذ تتألف هذه التسمية من مُحدِّدٍ (identifier) يُتبَع بالرمز @ مثل: abc@‎ أو fooBar@‎ وتُوضع قبل التعبير مباشرةً، مثل:

loop@ for (i in 1..100) {
    // ...
}

وبعد تسمية الحلقة نستطيع تقييدَ الأمر break أو continue بالتسمية كما في الشيفرة الآتية:

loop@ for (i in 1..100) {
    for (j in 1..100) {
        if (...) break@loop
    }
}

حيث ستنتهي الحلقة عند تحقُّق الشرط if والوصول إلى break التي بدورها ستنقل سياق البرنامج إلى نقطة التنفيذ التي تلي -مباشرةً- الحلقة ذات التسمية المذكورة (وهي loop)، أما continue فستنقل سياق البرنامج إلى التكرار التالي لتلك الحلقة.

العودة إلى نقطة مسماة (Label)

من المحتمل أن تتداخل الدوال بوجود قيم الدوال (literals) أو الدوال المحليّة (local) أو تعابير الكائن (object expressions)، وبالتالي فإن الكلمة المفتاحيّة return ستعود بسياق البرنامج من الدالة الخارجيّة وأكثر الحالات أهميةً هي العودة من تعبير lambda، ففي الشيفرة الآتية مثلًا:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return // العودة غير المحلية إلى حيث استدعيت الدالة foo()

        print(it)
    }
    println("this point is unreachable")
}

سيعود تعبير الرجوع return من أقرب دالة محيطة (enclosing) وهي في المثال هنا foo()‎، ويُلاحظ أنّ عمليات الرجوع غير المحليّة (non-local) هذه مدعومةٌ فقط في تعابير lambda المُمرَّرة إلى الدوال المباشرة (inline functions)، أما إن كان الرجوع من تعبير lambda فتجب حينئذٍ تسميته لتقييد return به، مثل:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // رجوع محلي إلى حيث استدعاء lambda
        // والذي هو فعليًا حلقة forEach

        print(it)
    }
    print(" done with explicit label")
}

إذ يتمّ الرجوع الآن فقط من تعبير lambda، ولكن من الأسهل في معظم الأحيان استخدامُ التسمية الضمنيّة (implicit) وهي تسميةٌ لها اسم الدالة نفسها التي يُمرَّر لها تعبير lambda أيّ:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach {
        if (it == 3) return@forEach // رجوع محلي إلى حيث استدعاء lambda
        // والذي هو فعليًا حلقة forEach

        print(it)
    }
    print(" done with implicit label")
}

وقد يُستبدَل تعبير lambda لتحل محله دالةٌ مجهولةٌ (anonymous function) إذ يكون أمر الرجوع return فيها إلى الدالة ذاتها، مثل:

fun foo() {
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // رجوع محلي إلى نفس المستدعي لتعبير lambda
        // أي حلقة forEach 

        print(it)
    }
    print(" done with explicit label")
}

ويُلاحظ أن استخدام الرجوع المحليّ في الأمثلة الثلاثة السابقة مشابهٌ لاستخدام continue في الحلقات العاديّة، ولكن لا يوجد ما يكافئ الأمر break، وأفضل طريقةٍ لمماثلته هي إضافة lambda أخرى متداخلة (nesting) والعودة غير المحليّة منها ، مثل:

fun foo() {
    run loop@{
        listOf(1, 2, 3, 4, 5).forEach {
            if (it == 3) return@loop // رجوع غير محلي من التعبير المُمَرَّر

            print(it)
        }
    }
    print(" done with nested loop")
}

وعند إعادة قيمةٍ ما فإن الأفضليّة تكون للرجوع المقيَّد أي أنّ الشيفرة:

return@a 1

تعني "إعادة القيمة 1 عند التسمية ‎@a" وليس "إعادة تعبيرٍ مُسمّىً باسم ‎@a 1".

مصادر