محتويات الدورة
Kotlin
أسس اللغة والحالة الصريحة
السلوك القابل للاستخدام والحدود المنمطة
التزامن والتكامل والتصميم المترابط

Kotlin / السلوك القابل للاستخدام والحدود المنمطة / الدرس 7

التحكم في التقييم بالدوال والتسلسلات

مرّر السلوك بوصفه بيانات، وميّز تحويلات المجموعات المتحمسة من تقييم Sequence الكسول.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • اقرأ أنواع الدوال وتوقع سلوك الحالة التي تلتقطها الدالة.
  • اشرح العمليات النهائية والتوقف المبكر ومخاطر الآثار الجانبية في التسلسلات.

للقيمة الدالية عقد للمعاملات والنتيجة

يصف نوع مثل (Int) -> Boolean دالة تقبل Int وتنتج Boolean. تقبل الدالة العليا دالة أخرى أو تعيدها، مما يسمح بتغيير سياسة الحساب دون نسخ عملية المرور المحيطة بها. توفر lambda قيمة دالية ذات جسم، بينما يشير مرجع قابل للاستدعاء مثل ::isPositive إلى دالة مسماة. اختر الصيغة التي توصل السياسة بوضوح. لا يجعل تمرير السلوك الدالة صرفة تلقائيا؛ فقد تقرأ حالة خارجية أو تعدل كائنا أو ترمي استثناء أو تجري إدخالا وإخراجا، ما لم يقيّد عقدها تلك الآثار.

احقن سياسة واحدة في عملية مرور ثابتة

تستخدم countMatching في المثال العملية count مع شرط يُمرَّر إليها. يسأل مستدعيان سؤالين مختلفين عن البيانات نفسها دون تغيير طريقة المرور. هذه نقطة امتداد محلية، وليست إطارا لحقن الاعتماديات. تفيد المعاملات الدالية خاصة عندما يكون التنوع المطلوب عملية صغيرة واحدة؛ وقد تكون واجهة أكبر أوضح عندما ترتبط عدة عمليات وقواعد عمر بعضها ببعض. أبق أسماء المعاملات وأنواع الرد صريحة عند الحدود العامة، حتى يعرف المستدعي هل يجوز للشرط تعديل العناصر التي يفحصها أم يجب أن يقتصر على قراءتها.

kotlin مرور واحد مع شرطين مختلفين
fun countMatching(values: List<Int>, predicate: (Int) -> Boolean): Int =
    values.count(predicate)

fun main() {
    val values = listOf(-2, 0, 3, 4)
    println(countMatching(values) { it > 0 })
    println(countMatching(values) { it % 2 == 0 })
}
الناتج المتوقع
2
3

المخرجان المتوقعان هما 2 ثم 3. القيم الموجبة هي 3 و4، والقيم الزوجية هي -2 و0 و4. البرنامج كامل لأغراض الشرح، لكنه لم يُترجم في هذه البيئة.

تُقرأ الحالة الملتقطة بحسب وقت التنفيذ

تستطيع lambda التقاط متغير من النطاق المحيط بها. وإذا كان المتغير قابلا للتعديل، فقد ترى الدالة عند استدعائها لاحقا قيمة تغيرت، لا القيمة التي كانت موجودة عند إنشائها. يهم ذلك عند تخزين النداءات المؤجلة أو بناء مسارات كسولة أو تنفيذ عمل متزامن. التقط لقطة ثابتة مقصودة عندما تكون تلك هي السياسة المطلوبة. لا تشارك عدادا قابلا للتعديل لمجرد سهولة التقاطه؛ فالمترجم لا يجعل تحديثاته المركبة ذرية. حتى القيمة الدالية الصغيرة تحتاج إلى ملكية وعمر وافتراضات خيوط واضحة إذا غادرت مستدعيها المباشر.

تؤجل Sequence العمل الوسيط حتى الاستهلاك

يكمل مسار القائمة المتحمس عادة تحويلا واحدا عبر المدخل كله قبل الخطوة التالية. أما Sequence فتستطيع تمرير العناصر عبر عدة عمليات وسيطة عندما تطلبها عملية نهائية. لذلك قد تمنع take تقييم العناصر اللاحقة غير اللازمة. يعد المثال مرات تقييم الشرط بدلا من ادعاء تحسن السرعة. قد يغير الكسل وقت حدوث الاستثناءات والآثار الجانبية، وقد يكرر الاستهلاك اللاحق العمل إذا سمح التسلسل بذلك. لا تُعد Sequence تعتمد على مورد إذا كان ملفها أو موردها الآخر سيُغلق قبل أن يبدأ المستدعي استهلاكها.

kotlin ملاحظة التقييم المؤجل والتوقف المبكر
fun main() {
    var inspected = 0
    val pipeline = listOf(1, 2, 3, 4).asSequence()
        .filter { inspected += 1; it % 2 == 0 }
        .take(1)
    println(inspected)
    println(pipeline.toList().joinToString())
    println(inspected)
}
الناتج المتوقع
0
2
2

المخرجات المتوقعة هي 0 ثم 2 ثم 2. لا يستدعي إنشاء المسار الشرط؛ وعند استهلاك أول قيمة زوجية يُفحص 1 ثم 2 ويتوقف المرور. العداد وسيلة لإظهار التقييم، وليس توصية باستخدام آثار جانبية في مسارات التحويل.

اختر الكسل وفقا للدلالة والعمل المقاس

قد تقلل التسلسلات التخصيصات الوسيطة أو المرور غير الضروري في أحمال معينة، لكنها تضيف كلفة للتكرار وليست أسرع تلقائيا للمجموعات الصغيرة. وتظل العمليات العامة مثل الفرز بحاجة إلى تخزين وسيط كبير. تحتاج التسلسلات اللانهائية مسارا نهائيا محدودا، وقد تحاول toList في موضع غير مناسب استهلاك تسلسل بلا نهاية. ابدأ بتنفيذ متحمس واضح، وحدد مشكلة فعلية في التخصيص أو المرور، ثم قارن حملا ممثلا في بيئة معزولة عندما تتوفر الأدوات. لا تدعي الدورة سوى ترتيب تقييم متوقع بالتحليل، ولا تدعي ترجمة البرنامج أو قياس أدائه.

الأخطاء الشائعة وإصلاحها

لا يفعل المسار شيئا قبل العملية النهائية.

لماذا يحدث؟
العمليات الوسيطة في Sequence كسولة ولا تطلب العناصر عند إنشائها.
كيف تصلحه؟
استهلك التسلسل بقصد، أو استخدم العمليات المتحمسة إذا كنت تريد عملا فوريا.
كيف تتجنب تكراره؟
أبق الآثار الجانبية خارج التحويلات المؤجلة.

يرى النداء المؤجل عتبة تغيرت.

لماذا يحدث؟
التقطت lambda متغيرا قابلا للتعديل، لا لقطة ثابتة لقيمته.
كيف تصلحه؟
التقط قيمة محلية ثابتة إذا كان معنى وقت إنشاء الدالة هو المطلوب.
كيف تتجنب تكراره؟
وثّق عمر النداء المؤجل وملكية الحالة التي يلتقطها.

لا تنتهي toList عند استهلاك تسلسل لانهائي.

لماذا يحدث؟
لم يكن للاستهلاك حد يوقف إنتاج العناصر.
كيف تصلحه؟
طبّق take بعدد مبرر أو شرط إنهاء مناسب آخر.
كيف تتجنب تكراره؟
حلّل العمليات النهائية قبل تقديم منتج غير محدود للعناصر.

اختبر فهمك

1. لماذا يطبع مثال Sequence العدد صفر أولا؟

اعرض الحل المشروح

تنشئ asSequence وfilter وtake وصفا لمعالجة مؤجلة. لا تطلب أي منها عنصرا حتى تُنفَّذ toList. تطلب العملية النهائية عندئذ أول عنصر مطابق، ولذلك لا يُفحص سوى 1 و2. أما filter المتحمسة فتفحص القائمة المعطاة كلها قبل أن تستقبل take نتيجتها.

2. متى تكون واجهة أوضح من ثلاثة معاملات lambda تبدو غير مترابطة؟

اعرض الحل المشروح

عندما تشترك العمليات في مسؤولية أو حالة أو عمر أو سياسة أخطاء واحدة. تستطيع واجهة مسماة توثيق هذه العلاقات ومنع المستدعي من خلط دوال غير متوافقة بطريق الخطأ. استخدم lambda لنقطة سلوك ضيقة، وكائنا متماسكا عندما يكون البروتوكول أوسع. ولا تعفي أي من الصيغتين من تحديد الآثار والملكية.

أهم ما تعلمته

  • تحمل القيم الدالية سلوكا وقد تحمل حالة ملتقطة أيضا.
  • يغيّر الكسل وقت التقييم وترتيبه والالتزامات المتعلقة بعمر الموارد.

المراجع الرسمية