Kotlin / أسس اللغة والحالة الصريحة / الدرس 1
تشغيل برنامج Kotlin وفهم القيم والأنواع
افهم مدخل برنامج موجّه إلى JVM، والفرق بين val وvar، واستنتاج الأنواع، والحساب، والتحويلات العددية الصريحة.
في هذه الصفحة
أهداف الدرس
- اشرح النوع الذي يستنتجه المترجم والوعد الذي يعبّر عنه تصريح المتغير.
- توقّع نتائج الحساب والطباعة مع التمييز بين مرجع لا يقبل إعادة الإسناد وكائن لا يقبل تعديل محتوياته.
افصل المصدر والترجمة والتنفيذ
تستهدف Kotlin منصات متعددة. تستخدم هذه الدورة أمثلة مصدر عادية موجّهة إلى JVM، ما لم يحدّد الدرس خلاف ذلك؛ أما Android وJavaScript وNative فلها مكتبات منصة ومتطلبات بناء مختلفة. تمثّل الدالة main المعرّفة خارج الأصناف نقطة دخول البرنامج، وتطبع println تمثيلا نصيا للقيمة يتبعه فاصل سطر. لا يُنفّذ المصدر بمجرد كتابته، بل يحتاج أداة Kotlin مناسبة لترجمته أو تقييمه. ووجود Java لا يثبت توفر مترجم Kotlin. لم يتوفر المترجم في بيئة التأليف، ولذلك تُعرض المخرجات كتوقعات مبنية على تحليل الشيفرة، وتبقى runnable بقيمة false.
اختر val ما لم تحتج إلى إعادة الإسناد
يُسنَد المتغير المحلي المعلن باستخدام val مرة واحدة، بينما يمكن إسناد قيمة أخرى إلى var ما دامت من النوع المعلن. يستنتج المترجم النوع من القيمة الابتدائية، لا من جميع الاستخدامات المستقبلية للمتغير. فالعدد المهيأ بقيمة صحيحة يُستنتج عادة بوصفه Int؛ وإسناد كسر إليه لاحقا خطأ توافق أنواع، وليس توسيعا تلقائيا للنوع. اختر أسماء ذات معنى ووحدات واضحة، مثل الدقائق أو الوحدات النقدية الصغرى. ينبغي أن يظل دور القيمة والمجال الذي تتوقعه الخوارزمية مفهوما للقارئ، حتى عندما يكون التصريح مختصرا.
fun main() {
val capacity = 8
var reserved = 2
reserved += 1
val remaining: Int = capacity - reserved
println("remaining=$remaining")
println("capacity type remains Int")
}
remaining=5
capacity type remains Int
السطران المتوقعان هما remaining=5 ثم العبارة النصية التي تذكر نوع capacity. لا يعاد إسناد capacity، بينما تزداد reserved قبل حساب remaining. هذا برنامج تعليمي كامل، لكنه لم يُترجم في بيئة التأليف.
تتبع القسمة أنواع المعاملات
إذا كان معاملا القسمة عددين صحيحين، تكون النتيجة صحيحة ويُحذف الجزء الكسري باتجاه الصفر. أما جعل أحد المعاملين Double فيؤدي إلى قسمة بأعداد عائمة. يغيّر تحويل صريح مثل toDouble نوع القيمة التي تدخل العملية؛ ولا يُعد التحويل القسري للنوع بديلا عاما للتحويل العددي. لا تستطيع الأعداد العائمة تمثيل كل كسر عشري بدقة. لذلك تحتاج قواعد المال إلى تمثيل مناسب أو مكتبة مجال ملائمة، ولا يكفي ظهور رقم عشري لإثبات دقة الحساب المحاسبي. وللأعداد الصحيحة أيضا مجالات محدودة، فتستلزم المدخلات الكبيرة تحليلا مقصودا لحدودها.
أظهر التحويل عند موضع الحساب
يقارن المثال بين نتيجة صحيحة ونتيجة عائمة باستخدام المدخلات الموجبة نفسها. تُدرج القوالب النصية قيمة باستخدام اسم يسبقه رمز الدولار أو تعبير داخل أقواس معقوفة. يغيّر القالب طريقة العرض ولا يغيّر دلالة الحساب؛ فالقسمة داخل الأقواس تظل خاضعة لأنواع معامليها. لا تعتمد على تنسيق المخرج لإصلاح عملية أُجريت أصلا بنوع غير مناسب. وعند الجمع بين أزمنة أو أحجام، حوّلها إلى وحدة مشتركة قبل الحساب وبيّن هل التقريب مقصود. المخرج هنا توقع لسلوك اللغة، وليس نتيجة قياس أداء.
fun main() {
val tasks = 5
val workers = 2
println(tasks / workers)
println(tasks.toDouble() / workers)
println("workers=$workers")
}
2
2.5
workers=2
المخرجات المتوقعة هي 2 ثم 2.5 ثم workers=2. يحفظ تحويل tasks إلى Double قبل القسمة الجزء الكسري؛ أما تحويل النتيجة الصحيحة بعد بتر الكسر فلا يستعيده. لم يُنفّذ البرنامج بالمترجم هنا.
ثبات المرجع لا يجمّد الكائن
يمكن أن تشير val إلى مجموعة قابلة للتعديل تتغير محتوياتها. فهي تمنع إسناد مجموعة أخرى إلى المتغير، ولا تمنع تعديل الكائن المشار إليه. وبالمثل قد تعرض واجهة للقراءة فقط كائنا يستطيع مرجع آخر تعديله. تهم هذه الفروق عند مشاركة الحالة بين الدوال أو المهام المتزامنة. ابدأ بأبسط قيمة تعبّر عن المجال، واجعل للتعديل مالكا صريحا. واستخدم أمثلة صغيرة حتمية أثناء التعلم، ثم أضف المدخلات واعتماديات المنصة عندما تفهم سلوك أخطائها وأعمارها؛ فسهولة كتابة المرجع لا تمنح الكائن ضمانات إضافية.
الأخطاء الشائعة وإصلاحها
لا يمكن إعادة إسناد val.
- لماذا يحدث؟
- استُخدم ربط للقراءة فقط لقيمة تتطلب الخوارزمية استبدالها لاحقا.
- كيف تصلحه؟
- استخدم var عندما تكون إعادة الإسناد مقصودة، أو احسب قيمة مشتقة في val جديدة.
- كيف تتجنب تكراره؟
- حدّد هل تحتاج الخوارزمية إلى حالة متغيرة أم إلى قيمة مشتقة يمكن تسميتها مستقلة.
يختفي الجزء الكسري من الناتج.
- لماذا يحدث؟
- كان معاملا القسمة صحيحين، فأُجريت قسمة صحيحة قبل عرض النتيجة.
- كيف تصلحه؟
- حوّل أحد المعاملين قبل القسمة إذا كانت الدلالة المطلوبة تتضمن كسورا.
- كيف تتجنب تكراره؟
- أدرج حالة لا تنتج قسمة كاملة ضمن الأمثلة والاختبارات.
تتغير قائمة مرتبطة بـval على غير المتوقع.
- لماذا يحدث؟
- اختلط منع إعادة إسناد المرجع بمنع تعديل الكائن نفسه.
- كيف تصلحه؟
- افحص المراجع الأخرى القادرة على التعديل، واختر ملكية واضحة أو نسخة دفاعية مناسبة.
- كيف تتجنب تكراره؟
- وثّق هل القيمة المعادة لقطة مستقلة أم منظور مشترك يمكن أن يتغير.
اختبر فهمك
1. توقّع ناتج 7 / 2 وناتج 7.toDouble() / 2، ثم فسّر الفرق.
اعرض الحل المشروح
الأول قيمة صحيحة مقدارها 3، والثاني قيمة Double مقدارها 3.5. يقع التحويل في التعبير الثاني قبل القسمة، ولذلك لا يُحذف الكسر. أما (7 / 2).toDouble() فينتج 3.0، لأن القسمة الصحيحة حذفت الكسر بالفعل قبل التحويل. اختر النوع الذي ستُجرى به العملية قبل محاولة تنسيق المخرج.
2. لا يعاد إسناد عدد المقاعد، بينما تكبر قائمة حجوزات قابلة للتعديل. أي الربطين يمكن تعريفه باستخدام val؟
اعرض الحل المشروح
يمكن استخدام val لكليهما. يبقى العدد بالقيمة نفسها، ويبقى مرجع القائمة مشيرا إلى الكائن نفسه حتى إن تغيّرت محتوياته. لكن هذا لا يجعل تعديل القائمة المتزامن آمنا، ولا يحمي قيد سعة الحجز. تحتاج تلك الضمانات إلى تصميم مستقل؛ فلا تستنتجها من اختيار val.
أهم ما تعلمته
- تحدد الأنواع معنى العمليات، بينما تتحكم val في إعادة الإسناد.
- المخرج المتوقع بالتحليل يختلف عن نتيجة فُحصت فعليا بالمترجم.