0Pricing
Scala for Backend Engineering & Functional Programming · درس

التعليق التوضيحي tailrec

تحسين مضمون

التعليق التوضيحي tailrec درس مجاني في Scala for Backend Engineering & Functional Programming على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Scala for Backend Engineering & Functional Programming، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Scala for Backend Engineering & Functional Programming 4 دروس في المجموع.

ما هو الاستدعاء الذاتي الذّيلي؟

يكون الاستدعاء الذاتي في الموضع الذّيلي عندما يكون آخر إجراء تنفذه الدالة تمامًا. ويمكن تحسين الدالة الذّيلية ذات الاستدعاء الذاتي لتصبح حلقة تعيد استخدام إطار مكدس واحد، ولذلك لا تتجاوز المكدس.

الموضع الذّيلي

في n * factorial(n-1)، لا يكون الاستدعاء الذاتي هو الأخير: إذ تُجرى عملية الضرب بعد عودته. أما في gcd(b, a % b)، فالاستدعاء هو الأخير. لذلك يكون الثاني فقط استدعاءً ذاتيًا ذيليًا.

التعليمة التوضيحية @tailrec

استورد scala.annotation.tailrec وأضف التعليمة التوضيحية إلى إحدى الدوال. يتحقق المترجم عندئذ من أن الاستدعاء يقع فعلًا في الموضع الذّيلي ويطبق التحسين. وإذا لم يكن كذلك، تفشل الترجمة.

import scala.annotation.tailrec

object Main {
  @tailrec
  def countdown(n: Int): Unit = {
    if (n >= 0) {
      println(n)
      countdown(n - 1)
    }
  }

  def main(args: Array[String]): Unit = countdown(3)
}

تحسين مضمون

الفائدة الأساسية من @tailrec هي الضمان وقت الترجمة. إذ ستعرف فورًا أن دالتك غير آمنة للمكدس، بدلًا من اكتشاف ذلك عبر تعطل وقت التشغيل عند استخدام مدخلات كبيرة.

دالة gcd ذات استدعاء ذاتي ذيلي

خوارزمية إقليدس ذات استدعاء ذاتي ذيلي أصلًا: فالاستدعاء الذاتي هو نتيجة جسم الدالة بالكامل. وتؤكد إضافة التعليمة التوضيحية ذلك.

import scala.annotation.tailrec

object Main {
  @tailrec
  def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)

  def main(args: Array[String]): Unit = {
    println(gcd(1071, 462))
  }
}

ما الذي يخرج الاستدعاء من الموضع الذّيلي

من الأنماط الشائعة التي تخرج الاستدعاء من الموضع الذّيلي:

  • إجراء عملية حسابية على النتيجة: n + f(...).
  • تغليفها في مُنشئ: x :: f(...).
  • استخدام النتيجة داخل كتلة try.

مثال غير ذّيلي

هذا المجموع ليس ذا استدعاء ذاتي ذيلي، لأن عملية الجمع تغلف الاستدعاء. وستؤدي إضافة @tailrec إليه إلى خطأ في الترجمة. (يُعرض من دون التعليمة التوضيحية لكي يعمل.)

object Main {
  def sum(n: Int): Int =
    if (n == 0) 0
    else n + sum(n - 1)

  def main(args: Array[String]): Unit = {
    println(sum(100))
  }
}

لماذا لا يمكن تحسينه

لأن n + sum(n - 1) يجب أن يتذكر n لإكمال الجمع بعد عودة الاستدعاء، يحتاج كل مستوى إلى إطار مكدس خاص به. ولا يستطيع المترجم اختزال ذلك إلى حلقة، ولذلك لا يكون الاستدعاء ذاتيًا ذيليًا.

حلقة كبيرة ذات استدعاء ذاتي ذيلي

يستمر مجموع ذو استدعاء ذاتي ذيلي ويستخدم مُجمِّعًا في العمل مع مدخلات ضخمة من دون تجاوز المكدس، لأنه يعيد استخدام إطار واحد.

import scala.annotation.tailrec

object Main {
  @tailrec
  def sumTo(n: Int, acc: Long = 0): Long =
    if (n == 0) acc else sumTo(n - 1, acc + n)

  def main(args: Array[String]): Unit = {
    println(sumTo(1000000))
  }
}

تتطلب tailrec دالة نهائية أو محلية

لكي تنطبق @tailrec، يجب ألا تكون الدالة قابلة للتجاوز: يجب أن تكون private أو final، أو دالة محلية/متداخلة. فقد يُتجاوزَت الدالة المفتوحة، مما يكسر التحسين، ولذلك يرفضها المترجم.

تنبيه بشأن الاستدعاء الذاتي المتبادل

تحسّن @tailrec دالة تستدعي نفسها فقط. ولا يمكن تحسين دالتين تستدعي كل منهما الأخرى (الاستدعاء الذاتي المتبادل) ذيليًا بواسطة JVM مباشرة؛ وتحتاج إلى الترامبولين لهذا الغرض، كما سيأتي لاحقًا.

تحقق سريع

اختبر مدى فهمك لـ @tailrec.

مراجعة

لقد تعلمت التعليمة التوضيحية @tailrec:

  • يمكن تحسين الاستدعاء الموجود في الموضع الذّيلي ليصبح حلقة.
  • تمنح @tailrec ضمانًا وقت الترجمة لسلامة المكدس.
  • يجب أن تكون الدالة final أو private أو محلية.
  • تنطبق على الاستدعاء الذاتي للنفس فقط، لا على الاستدعاء الذاتي المتبادل.

الأسئلة الشائعة

هل درس «التعليق التوضيحي tailrec» مجاني؟

نعم — نص درس «التعليق التوضيحي tailrec» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Scala for Backend Engineering & Functional Programming، انتقل إلى CoddyKit PRO. تتضمن دورة Scala for Backend Engineering & Functional Programming 4 دروس في المجموع.

ماذا ستتعلم في «التعليق التوضيحي tailrec»؟

تحسين مضمون تتمرن على Scala for Backend Engineering & Functional Programming مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Scala for Backend Engineering & Functional Programming؟

لا تُشترط خبرة سابقة. Scala for Backend Engineering & Functional Programming على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «التعليق التوضيحي tailrec»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Scala for Backend Engineering & Functional Programming هذا؟

نعم. كل درس في Scala for Backend Engineering & Functional Programming يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. أساسيات الاستدعاء الذاتي
  2. التعليق التوضيحي tailrec
  3. نمط المُراكِم
  4. Trampolining
← العودة إلى Scala for Backend Engineering & Functional Programming