Kotlin Bootcamp for Programmers 3: Functions

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

مقدمة

في هذا الدرس العملي، ستنشئ برنامجًا بلغة Kotlin وتتعرّف على الدوال في Kotlin، بما في ذلك القيم التلقائية للمعلمات والفلاتر وعبارات lambda والدوال المدمجة.

بدلاً من إنشاء تطبيق نموذجي واحد، تم تصميم الدروس في هذه الدورة التدريبية لتعزيز معرفتك، ولكنها شبه مستقلة عن بعضها البعض حتى تتمكن من تصفّح الأقسام التي تعرفها. ولربطها ببعضها، تستخدم العديد من الأمثلة سمة حوض السمك. إذا أردت الاطّلاع على قصة حوض السمك بالكامل، يمكنك الرجوع إلى دورة Kotlin Bootcamp للمبرمجين على Udacity.

ما يجب معرفته

  • أساسيات لغة برمجة حديثة تعتمد على العناصر وتتضمّن كتابة رمزية ثابتة
  • كيفية البرمجة باستخدام الفئات والطرق ومعالجة الاستثناءات بلغة واحدة على الأقل
  • كيفية استخدام REPL (حلقة القراءة والتقييم والطباعة) في Kotlin في IntelliJ IDEA
  • أساسيات Kotlin، بما في ذلك الأنواع والعوامل والحلقات

هذا الدرس التطبيقي حول الترميز مخصّص للمبرمجين الذين يعرفون لغة برمجة تعتمد على العناصر ويريدون معرفة المزيد عن Kotlin.

أهداف الدورة التعليمية

  • كيفية إنشاء برنامج باستخدام دالة main() ووسيطات في IntelliJ IDEA
  • كيفية استخدام القيم التلقائية والدوال المدمجة
  • كيفية تطبيق فلاتر على القوائم
  • كيفية إنشاء تعابير lambda الأساسية والدوال ذات الترتيب الأعلى

الإجراءات التي ستنفذّها

  • استخدِم REPL لتجربة بعض الرموز البرمجية.
  • استخدِم IntelliJ IDEA لإنشاء برامج Kotlin الأساسية.

في هذه المهمة، ستنشئ برنامج Kotlin وتتعرّف على الدالة main()، بالإضافة إلى كيفية تمرير وسيطات إلى برنامج من سطر الأوامر.

قد تتذكر الدالة printHello() التي أدخلتها في REPL في تجربة عملية سابقة:

fun printHello() {
    println ("Hello World")
}

printHello()
⇒ Hello World

يمكنك تحديد الدوال باستخدام الكلمة الرئيسية fun، يليها اسم الدالة. كما هو الحال مع لغات البرمجة الأخرى، تُستخدَم الأقواس () لوسيطات الدالة، إن وجدت. تحيط الأقواس المعقوفة {} بالرمز البرمجي للدالة. لا يوجد نوع إرجاع لهذه الدالة، لأنّها لا تُرجع أي قيمة.

الخطوة 1: إنشاء ملف Kotlin

  1. افتح IntelliJ IDEA.
  2. تعرض لوحة المشروع على يمين الشاشة في IntelliJ IDEA قائمة بملفات مشروعك ومجلداته. ابحث عن المجلد src ضمن Hello Kotlin وانقر عليه بزر الماوس الأيمن. (يجب أن يكون لديك مشروع Hello Kotlin من الدرس السابق حول الترميز).
  3. اختَر جديد > ملف / فئة Kotlin.
  4. اترك النوع على ملف، وأطلِق على الملف اسم Hello.
  5. انقر على موافق.

يتوفّر الآن ملف في المجلد src باسم Hello.kt.

الخطوة 2: إضافة الرمز البرمجي وتشغيل البرنامج

  1. كما هو الحال مع اللغات الأخرى، تحدّد الدالة main() في Kotlin نقطة الدخول للتنفيذ. يتم تمرير أي وسيطة سطر أوامر كمصفوفة من السلاسل.

    اكتب الرمز التالي أو الصقه في الملف Hello.kt :
fun main(args: Array<String>) {
    println("Hello, world!")
}

مثل دالة printHello() السابقة، لا تحتوي هذه الدالة على عبارة return. تعرض كل دالة في Kotlin قيمة، حتى عندما لا يتم تحديد أي شيء بشكل صريح. لذا، تعرض دالة مثل main() النوع kotlin.Unit، وهو طريقة Kotlin للإشارة إلى عدم توفّر قيمة.

  1. لتشغيل البرنامج، انقر على المثلث الأخضر على يمين الدالة main(). اختَر تشغيل HelloKt من القائمة.
  2. تُجمِّع IntelliJ IDEA البرنامج وتشغّله. تظهر النتائج في لوحة سجلّ في أسفل الشاشة، كما هو موضّح أدناه.

الخطوة 3: تمرير وسيطات إلى الدالة main()

بما أنّك تشغّل برنامجك من IntelliJ IDEA وليس من سطر الأوامر، عليك تحديد أي وسيطات للبرنامج بطريقة مختلفة قليلاً.

  1. انقر على تشغيل > تعديل الإعدادات. تفتح نافذة إعدادات التشغيل/تصحيح الأخطاء.
  2. اكتب Kotlin! في حقل وسيطات البرنامج.
  3. انقر على موافق.

الخطوة 4: تغيير الرمز البرمجي لاستخدام نموذج سلسلة

يُدرج نموذج السلسلة متغيّرًا أو تعبيرًا في سلسلة، ويشير $ إلى أنّ جزءًا من السلسلة سيكون متغيّرًا أو تعبيرًا. تحيط الأقواس المعقوفة {} بالتعبير، إن وُجد.

  1. في ملف Hello.kt، غيِّر رسالة الترحيب لاستخدام الوسيط الأول الذي تم تمريره إلى البرنامج، args[0]، بدلاً من "world".
fun main(args: Array<String>) {
    println("Hello, ${args[0]}")
}
  1. شغِّل البرنامج، وسيتضمّن الناتج الوسيطة التي حدّدتها.
⇒ Hello, Kotlin!

في هذه المهمة، ستتعرّف على سبب احتواء كل عنصر تقريبًا في Kotlin على قيمة، وسبب أهمية ذلك.

تتضمّن بعض اللغات الأخرى عبارات، وهي أسطر من الرموز البرمجية لا تتضمّن قيمة. في Kotlin، كل شيء تقريبًا هو تعبير وله قيمة، حتى إذا كانت هذه القيمة هي kotlin.Unit.

  1. في ملف Hello.kt، اكتب الرمز البرمجي في main() لتعيين println() إلى متغيّر باسم isUnit وطباعته. (لا تعرض الدالة println() قيمة، لذا تعرض kotlin.Unit).
// Will assign kotlin.Unit
val isUnit = println("This is an expression")
println(isUnit)
  1. تشغيل برنامجك يطبع println() السلسلة "This is an expression". يطبع println() الثاني قيمة عبارة println() الأولى، أي kotlin.Unit.
⇒ This is an expression
kotlin.Unit
  1. عرِّف متغيّرًا باسم val وسمِّه temperature واضبط قيمته على 10.
  2. عرِّف val آخر باسم isHot وحدِّد قيمة الإرجاع لعبارة if/else على أنّها isHot، كما هو موضّح في الرمز التالي. بما أنّها عبارة عن تعبير، يمكنك استخدام قيمة التعبير if على الفور.
val temperature = 10
val isHot = if (temperature > 50) true else false
println(isHot)
⇒ false
  1. استخدِم قيمة تعبير في نموذج سلسلة. أضِف بعض الرموز البرمجية للتحقّق من درجة الحرارة لتحديد ما إذا كانت السمكة آمنة أم دافئة جدًا، ثم شغِّل برنامجك.
val temperature = 10
val message = "The water temperature is ${ if (temperature > 50) "too warm" else "OK" }."
println(message)
⇒ The water temperature is OK.

في هذه المهمة، ستتعرّف على المزيد من المعلومات عن الدوال في Kotlin، وعن التعبير الشرطي when المفيد جدًا.

الخطوة 1: إنشاء بعض الدوال

في هذه الخطوة، ستجمع بعض ما تعلّمته وتنشئ دوالاً بأنواع مختلفة. يمكنك استبدال محتوى الملف Hello.kt بهذا الرمز الجديد.

  1. اكتب دالة باسم feedTheFish() تستدعي randomDay() للحصول على يوم عشوائي من أيام الأسبوع. استخدِم نموذج سلسلة لطباعة food لتأكله السمكة في ذلك اليوم. في الوقت الحالي، تتناول الأسماك الطعام نفسه كل يوم.
fun feedTheFish() {
    val day = randomDay()
    val food = "pellets"
    println ("Today is $day and the fish eat $food")
}

fun main(args: Array<String>) {
    feedTheFish()
}
  1. اكتب الدالة randomDay() لاختيار يوم عشوائي من مصفوفة وعرضه.

تأخذ الدالة nextInt() حدًا صحيحًا، ما يحدّ من الرقم من Random() إلى 0 من خلال 6 لمطابقة الصفيف week.

fun randomDay() : String {
    val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
            "Friday", "Saturday", "Sunday")
    return week[Random().nextInt(week.size)]
}
  1. يتم تحديد الدالتَين Random() وnextInt() في java.util.*. في أعلى الملف، أضِف عملية الاستيراد اللازمة:
import java.util.*    // required import
  1. شغِّل البرنامج وتحقّق من الناتج.
⇒ Today is Tuesday and the fish eat pellets

الخطوة 2: استخدام تعبير when

لتعزيز ذلك، غيِّر الرمز لاختيار طعام مختلف لأيام مختلفة باستخدام تعبير when. تشبه عبارة when عبارة switch في لغات البرمجة الأخرى، ولكن when تتوقف تلقائيًا في نهاية كل فرع. ويضمن أيضًا أنّ الرمز البرمجي يغطي جميع الفروع إذا كنت تتحقّق من تعداد.

  1. في ملف Hello.kt، أضِف دالة باسم fishFood() تأخذ يومًا كـ String وتعرض طعام السمكة لهذا اليوم كـ String. استخدِم when()، لكي تحصل السمكة على طعام محدّد كل يوم. نفِّذ برنامجك بضع مرات للاطّلاع على نتائج مختلفة.
fun fishFood (day : String) : String {
    var food = ""
    when (day) {
        "Monday" -> food = "flakes"
        "Tuesday" -> food = "pellets"
        "Wednesday" -> food = "redworms"
        "Thursday" -> food = "granules"
        "Friday" -> food = "mosquitoes"
        "Saturday" -> food = "lettuce"
        "Sunday" -> food = "plankton"
    }
    return food
}

fun feedTheFish() {
    val day = randomDay()
    val food = fishFood(day)

    println ("Today is $day and the fish eat $food")
}
⇒ Today is Thursday and the fish eat granules
  1. أضِف فرعًا تلقائيًا إلى التعبير when باستخدام else. لأغراض الاختبار، وللتأكّد من استخدام القيمة التلقائية في برنامجك في بعض الأحيان، أزِل الفرعَين Tuesday وSaturday.

    يضمن توفّر فرع تلقائي حصول food على قيمة قبل إرجاعها، وبالتالي لن تحتاج إلى تهيئتها بعد الآن. بما أنّ الرمز البرمجي يحدّد الآن سلسلة لـ food مرة واحدة فقط، يمكنك تعريف food باستخدام val بدلاً من var.
fun fishFood (day : String) : String {
    val food : String
    when (day) {
        "Monday" -> food = "flakes"
        "Wednesday" -> food = "redworms"
        "Thursday" -> food = "granules"
        "Friday" -> food = "mosquitoes"
        "Sunday" -> food = "plankton"
        else -> food = "nothing"
    }
    return food
}
  1. بما أنّ كل تعبير له قيمة، يمكنك جعل هذا الرمز أكثر إيجازًا. عرض قيمة التعبير when مباشرةً وإزالة المتغيّر food قيمة التعبير when هي قيمة التعبير الأخير للفرع الذي استوفى الشرط.
fun fishFood (day : String) : String {
    return when (day) {
        "Monday" -> "flakes"
        "Wednesday" -> "redworms"
        "Thursday" -> "granules"
        "Friday" -> "mosquitoes"
        "Sunday" -> "plankton"
        else -> "nothing"
    }
}

يبدو الإصدار النهائي من برنامجك على النحو الموضّح في الرمز البرمجي أدناه.

import java.util.*    // required import

fun randomDay() : String {
    val week = arrayOf ("Monday", "Tuesday", "Wednesday", "Thursday",
        "Friday", "Saturday", "Sunday")
    return week[Random().nextInt(week.size)]
}

fun fishFood (day : String) : String {
    return when (day) {
        "Monday" -> "flakes"
        "Wednesday" -> "redworms"
        "Thursday" -> "granules"
        "Friday" -> "mosquitoes"
        "Sunday" -> "plankton"
        else -> "nothing"
    }
}

fun feedTheFish() {
    val day = randomDay()
    val food = fishFood(day)
    println ("Today is $day and the fish eat $food")
}

fun main(args: Array<String>) {
    feedTheFish()
}

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

الخطوة 1: إنشاء قيمة تلقائية لمَعلمة

في Kotlin، يمكنك تمرير الوسيطات حسب اسم المَعلمة. يمكنك أيضًا تحديد قيم تلقائية للمَعلمات: إذا لم يقدّم المتصل وسيطة، يتم استخدام القيمة التلقائية. في وقت لاحق، عند كتابة الطرق (الدوال الأعضاء)، يعني ذلك أنّه يمكنك تجنُّب كتابة العديد من إصدارات التحميل الزائد للطريقة نفسها.

  1. في ملف Hello.kt، اكتب الدالة swim() مع المَعلمة String التي تحمل الاسم speed والتي تطبع سرعة السمكة. تتضمّن المَعلمة speed القيمة التلقائية "fast".
fun swim(speed: String = "fast") {
   println("swimming $speed")
}
  1. من الدالة main()، استدعِ الدالة swim() بثلاث طرق. عليك أولاً استدعاء الدالة باستخدام القيمة التلقائية. بعد ذلك، استدعِ الدالة ومرِّر المَعلمة speed بدون اسم، ثم استدعِ الدالة من خلال تسمية المَعلمة speed.
swim()   // uses default speed
swim("slow")   // positional argument
swim(speed="turtle-like")   // named parameter
⇒ swimming fast
swimming slow
swimming turtle-like

الخطوة 2: إضافة المَعلمات المطلوبة

إذا لم يتم تحديد قيمة تلقائية لمَعلمة، يجب دائمًا تمرير الوسيطة المقابلة.

  1. في ملف Hello.kt، اكتب الدالة shouldChangeWater() التي تتضمّن ثلاث مَعلمات: day وtemperature ومستوى dirty. تعرض الدالة القيمة true إذا كان يجب تغيير الماء، ويحدث ذلك إذا كان اليوم هو الأحد أو إذا كانت درجة الحرارة مرتفعة جدًا أو إذا كان الماء متسخًا جدًا. يجب تحديد يوم الأسبوع، ولكن درجة الحرارة التلقائية هي 22 ومستوى التلوث التلقائي هو 20.

    استخدِم تعبير when بدون وسيط، وهو ما يعمل في Kotlin كسلسلة من عمليات التحقّق if/else if.
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
    return when {
        temperature > 30 -> true
        dirty > 30 -> true
        day == "Sunday" ->  true
        else -> false
    }
}
  1. اتّصِل بـ "shouldChangeWater()" من "feedTheFish()" وقدِّم اليوم. لا تتضمّن المَعلمة day قيمة تلقائية، لذا يجب تحديد وسيط. تتضمّن المَعلمتان الأخريان للدالة shouldChangeWater() قيمًا تلقائية، لذا ليس عليك تمرير وسيطات لهما.
fun feedTheFish() {
    val day = randomDay()
    val food = fishFood(day)
    println ("Today is $day and the fish eat $food")
    println("Change water: ${shouldChangeWater(day)}")
}
=> Today is Thursday and the fish eat granules
Change water: false

الخطوة 3: إنشاء دوال مضغوطة

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

الدوال المدمجة، أو الدوال ذات التعبير الواحد، هي نمط شائع في Kotlin. عندما تعرض الدالة نتائج تعبير واحد، يمكنك تحديد نص الدالة بعد الرمز = وحذف الأقواس المعقوفة {} والرمز return.

  1. في ملف Hello.kt، أضِف دوالاً مضغوطة لاختبار الشروط.
fun isTooHot(temperature: Int) = temperature > 30

fun isDirty(dirty: Int) = dirty > 30

fun isSunday(day: String) = day == "Sunday"
  1. غيِّر shouldChangeWater() لاستدعاء الدوال الجديدة.
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20): Boolean {
    return when {
        isTooHot(temperature) -> true
        isDirty(dirty) -> true
        isSunday(day) -> true
        else  -> false
    }
}
  1. تشغيل برنامجك يجب أن يكون الناتج من println() مع shouldChangeWater() هو نفسه الناتج قبل التبديل إلى استخدام الدوال المدمجة.

القيم التلقائية

ليس من الضروري أن تكون القيمة التلقائية للمَعلمة قيمة. يمكن أن تكون دالة أخرى، كما هو موضّح في النموذج الجزئي التالي:

fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = getDirtySensorReading()): Boolean {
    ...

في هذه المهمة، ستتعرّف على بعض المعلومات حول الفلاتر في Kotlin. تُعدّ الفلاتر طريقة مفيدة للحصول على جزء من قائمة استنادًا إلى بعض الشروط.

الخطوة 1: إنشاء فلتر

  1. في ملف Hello.kt، حدِّد قائمة بزخارف حوض السمك على المستوى الأعلى باستخدام listOf(). يمكنك استبدال محتوى الملف Hello.kt.
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
  1. أنشئ دالة main() جديدة تتضمّن سطرًا لطباعة الزخارف التي تبدأ بالحرف "p" فقط. يظهر رمز شرط الفلتر بين قوسين معقوفين {}، ويشير it إلى كل عنصر أثناء تكرار الفلتر. إذا كان التعبير يعرض true، يتم تضمين العنصر.
fun main() {
    println( decorations.filter {it[0] == 'p'})
}
  1. شغِّل برنامجك، وسيظهر لك الناتج التالي في نافذة التشغيل:
⇒ [pagoda, plastic plant]

الخطوة 2: مقارنة الفلاتر النشطة وغير النشطة

إذا كنت معتادًا على الفلاتر بلغات أخرى، قد تتساءل عمّا إذا كانت الفلاتر في Kotlin تُنفَّذ على الفور أو بشكل مؤجّل. هل يتم إنشاء قائمة النتائج على الفور، أم عند الوصول إلى القائمة؟ في Kotlin، يحدث ذلك بالطريقة التي تحتاج إليها. يكون الخيار filter نشطًا تلقائيًا، وفي كل مرة تستخدم فيها الفلتر، يتم إنشاء قائمة.

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

  1. في ملف Hello.kt، غيِّر الرمز البرمجي لتعيين القائمة التي تمّت فلترتها إلى متغيّر باسم eager، ثم اطبعها.
fun main() {
    val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")

    // eager, creates a new list
    val eager = decorations.filter { it [0] == 'p' }
    println("eager: " + eager)
  1. أسفل هذا الرمز، قيِّم الفلتر باستخدام Sequence مع asSequence(). عيِّن التسلسل إلى متغيّر باسم filtered، ثم اطبع التسلسل.
   // lazy, will wait until asked to evaluate
    val filtered = decorations.asSequence().filter { it[0] == 'p' }
    println("filtered: " + filtered)

عندما تعرض نتائج الفلتر كـ Sequence، لن يحتوي المتغيّر filtered على قائمة جديدة، بل سيحتوي على Sequence لعناصر القائمة ومعلومات عن الفلتر الذي سيتم تطبيقه على هذه العناصر. عند الوصول إلى عناصر Sequence، يتم تطبيق الفلتر، ويتم عرض النتيجة لك.

  1. فرض تقييم التسلسل عن طريق تحويله إلى List باستخدام toList() اطبع النتيجة.
    // force evaluation of the lazy list
    val newList = filtered.toList()
    println("new list: " + newList)
  1. شغِّل برنامجك وراقِب الناتج.
⇒ eager: [pagoda, plastic plant]
filtered: kotlin.sequences.FilteringSequence@386cc1c4
new list: [pagoda, plastic plant]

لتوضيح ما يحدث مع Sequence والتقييم الكسول، استخدِم الدالة map(). تنفّذ الدالة map() عملية تحويل بسيطة على كل عنصر في التسلسل.

  1. باستخدام قائمة decorations نفسها كما هو موضّح أعلاه، يمكنك إجراء عملية تحويل باستخدام map() لا تفعل شيئًا، وتعرض ببساطة العنصر الذي تم تمريره. أضِف println() لعرض كل مرة يتم فيها الوصول إلى عنصر، ثمّ خصّص التسلسل لمتغيّر باسم lazyMap.
    val lazyMap = decorations.asSequence().map {
        println("access: $it")
        it
    }
  1. طباعة lazyMap، وطباعة العنصر الأول من lazyMap باستخدام first()، وطباعة lazyMap بعد تحويلها إلى List
    println("lazy: $lazyMap")
    println("-----")
    println("first: ${lazyMap.first()}")
    println("-----")
    println("all: ${lazyMap.toList()}")
  1. شغِّل برنامجك وراقِب الناتج. عند طباعة lazyMap، تتم طباعة مرجع إلى Sequence فقط، ولا يتم استدعاء println() الداخلي. تؤدي طباعة العنصر الأول إلى الوصول إلى العنصر الأول فقط. يؤدي تحويل Sequence إلى List إلى الوصول إلى جميع العناصر.
⇒ lazy: kotlin.sequences.TransformingSequence@5ba23b66
-----
access: rock
first: rock
-----
access: rock
access: pagoda
access: plastic plant
access: alligator
access: flowerpot
all: [rock, pagoda, plastic plant, alligator, flowerpot]
  1. أنشئ Sequence جديدًا باستخدام الفلتر الأصلي قبل تطبيق map. اطبع تلك النتيجة.
    val lazyMap2 = decorations.asSequence().filter {it[0] == 'p'}.map {
        println("access: $it")
        it
    }
    println("-----")
    println("filtered: ${ lazyMap2.toList() }")
  1. نفِّذ برنامجك ولاحظ الناتج الإضافي. كما هو الحال مع الحصول على العنصر الأول، لا يتم استدعاء println() الداخلي إلا للعناصر التي يتم الوصول إليها.
⇒
-----
access: pagoda
access: plastic plant
filtered: [pagoda, plastic plant]

في هذه المهمة، ستتعرّف على تعبيرات lambda والدوال ذات الترتيب الأعلى في Kotlin.

Lambdas

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

الدوال ذات الترتيب الأعلى

يمكنك إنشاء دالة من الدرجة العليا من خلال تمرير تعبير lambda إلى دالة أخرى. في المهمة السابقة، أنشأت دالة ترتيب أعلى باسم filter. لقد مرّرت تعبير lambda التالي إلى filter كشرط للتحقّق منه:
{it[0] == 'p'}

وبالمثل، map هي دالة من الدرجة العليا، وكان رمز lambda الذي مرّرته إليها هو عملية التحويل التي سيتم تطبيقها.

الخطوة 1: التعرّف على دوال lambda

  1. مثل الدوال المُسمّاة، يمكن أن تحتوي دوال lambda على مَعلمات. بالنسبة إلى lambdas، يتم وضع المَعلمات (وأنواعها، إذا لزم الأمر) على يسار ما يُعرف بسهم الدالة ->. يتم وضع الرمز البرمجي المطلوب تنفيذه على يسار سهم الدالة. بعد تعيين تعبير lambda لمتغيّر، يمكنك استدعاؤه تمامًا مثل الدالة.

    باستخدام REPL (أدوات > Kotlin > Kotlin REPL)، جرِّب هذا الرمز:
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10

في هذا المثال، تأخذ دالة lambda وسيطة Int باسم dirty، وتعرض dirty / 2. (لأنّ الفلترة تزيل الأوساخ).

  1. إنّ بنية Kotlin لأنواع الدوال مرتبطة ارتباطًا وثيقًا ببنيتها لتعابير lambda. استخدِم هذه البنية لتعريف متغيّر يحتوي على دالة بشكل واضح:
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }

في ما يلي ما يشير إليه الرمز:

  • أنشئ متغيّرًا باسم waterFilter.
  • يمكن أن تكون waterFilter أي دالة تأخذ Int وتعرض Int.
  • عيِّن دالة lambda إلى waterFilter.
  • تعرض دالة lambda قيمة الوسيطة dirty مقسومة على 2.

يُرجى العِلم أنّه لم يعُد عليك تحديد نوع وسيطة lambda. يتم احتساب النوع من خلال استنتاج النوع.

الخطوة 2: إنشاء دالة ترتيب أعلى

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

  1. اكتب دالة من الدرجة العليا. في ما يلي مثال أساسي، وهو دالة تأخذ وسيطتَين. الوسيطة الأولى هي عدد صحيح. الوسيطة الثانية هي دالة تأخذ عددًا صحيحًا وتعرض عددًا صحيحًا. جرِّبها في REPL.
fun updateDirty(dirty: Int, operation: (Int) -> Int): Int {
   return operation(dirty)
}

يستدعي نص الرمز الدالة التي تم تمريرها كوسيطة ثانية، ويمرر الوسيطة الأولى إليها.

  1. لاستدعاء هذه الدالة، مرِّر إليها عددًا صحيحًا ودالة.
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }
println(updateDirty(30, waterFilter))
⇒ 15

لا يجب أن تكون الدالة التي تمرّرها lambda، بل يمكن أن تكون دالة مُسمّاة عادية بدلاً من ذلك. لتحديد الوسيطة كدالة عادية، استخدِم عامل التشغيل ::. بهذه الطريقة، يعرف Kotlin أنّك تمرّر مرجع الدالة كوسيطة، وليس محاولة استدعاء الدالة.

  1. جرِّب تمرير دالة عادية مُسمّاة إلى updateDirty().
fun increaseDirty( start: Int ) = start + 1

println(updateDirty(15, ::increaseDirty))
⇒ 16
var dirtyLevel = 19;
dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}
println(dirtyLevel)
⇒ 42
  • لإنشاء ملف مصدر Kotlin في IntelliJ IDEA، ابدأ بمشروع Kotlin.
  • لتجميع برنامج وتشغيله في IntelliJ IDEA، انقر على المثلث الأخضر بجانب الدالة main(). يظهر الناتج في نافذة سجل أدناه.
  • في IntelliJ IDEA، حدِّد وسيطات سطر الأوامر لتمريرها إلى الدالة main() في تشغيل > تعديل الإعدادات.
  • كل شيء تقريبًا في Kotlin له قيمة. يمكنك استخدام هذه الحقيقة لجعل الرمز البرمجي أكثر إيجازًا من خلال استخدام قيمة if أو when كتعبير أو قيمة معروضة.
  • تزيل الوسيطات التلقائية الحاجة إلى إصدارات متعددة من دالة أو طريقة. على سبيل المثال:
    fun swim(speed: String = "fast") { ... }
  • يمكن أن تجعل الدوال المختصرة، أو دوال التعبير الواحد، الرمز البرمجي أكثر قابلية للقراءة. على سبيل المثال:
    fun isTooHot(temperature: Int) = temperature > 30
  • لقد تعرّفت على بعض الأساسيات حول الفلاتر التي تستخدم تعابير lambda. على سبيل المثال:
    val beginsWithP = decorations.filter { it [0] == 'p' }
  • تعبير lambda هو تعبير ينشئ دالة غير مُسمّاة. يتم تحديد تعابير Lambda بين قوسَين معقوفَين {}.
  • في دالة من الرتبة العليا، يمكنك تمرير دالة، مثل تعبير lambda، إلى دالة أخرى كبيانات. على سبيل المثال:
    dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}

يتضمّن هذا الدرس الكثير من المعلومات، خاصةً إذا كنت جديدًا على تعبيرات lambda. يتناول درس لاحق دوال lambda والدوال ذات الترتيب الأعلى.

مستندات Kotlin

إذا أردت الحصول على مزيد من المعلومات حول أي موضوع في هذه الدورة التدريبية، أو إذا واجهتك أي مشكلة، يمكنك الانتقال إلى https://kotlinlang.org.

برامج تعليمية حول Kotlin

يتضمّن الموقع الإلكتروني https://try.kotlinlang.org برامج تعليمية غنية بصريًا تُعرف باسم Kotlin Koans، ومترجم مستند إلى الويب، ومجموعة كاملة من المستندات المرجعية مع أمثلة.

دورة Udacity التدريبية

للاطّلاع على دورة Udacity التدريبية حول هذا الموضوع، يُرجى الانتقال إلى برنامج Kotlin التدريبي للمبرمجين.

IntelliJ IDEA

يمكنك العثور على مستندات IntelliJ IDEA على موقع JetBrains الإلكتروني.

يسرد هذا القسم مهامًا منزلية محتملة للطلاب الذين يعملون على هذا الدرس التطبيقي العملي كجزء من دورة تدريبية يقودها مدرّب. على المعلّم تنفيذ ما يلي:

  • حدِّد واجبًا منزليًا إذا لزم الأمر.
  • توضيح كيفية إرسال الواجبات المنزلية للطلاب
  • وضع درجات للواجبات المنزلية

يمكن للمدرّبين استخدام هذه الاقتراحات بالقدر الذي يريدونه، ويجب ألا يترددوا في تكليف الطلاب بأي واجبات منزلية أخرى يرونها مناسبة.

إذا كنت تعمل على هذا الدرس العملي بنفسك، يمكنك استخدام مهام الواجب المنزلي هذه لاختبار معلوماتك.

الإجابة عن هذه الأسئلة

السؤال 1

تعرض الدالة contains(element: String) القيمة true إذا كانت السلسلة element مضمّنة في السلسلة التي يتم استدعاؤها فيها. ما هو مُخرج الرمز التالي؟

val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")

println(decorations.filter {it.contains('p')})

[pagoda, plastic, plant]

[pagoda, plastic plant]

[pagoda, plastic plant, flowerpot]

[rock, alligator]

السؤال 2

في تعريف الدالة التالي، أيّ من المَعلمات مطلوب؟
fun shouldChangeWater (day: String, temperature: Int = 22, dirty: Int = 20, numDecorations: Int = 0): Boolean {...}

numDecorations

dirty

day

temperature

السؤال 3

يمكنك تمرير دالة عادية مُسمّاة (وليس نتيجة استدعائها) إلى دالة أخرى. كيف يمكنك نقل increaseDirty( start: Int ) = start + 1 إلى updateDirty(dirty: Int, operation: (Int) -> Int)؟

updateDirty(15, &increaseDirty())

updateDirty(15, increaseDirty())

updateDirty(15, ("increaseDirty()"))

updateDirty(15, ::increaseDirty)

انتقِل إلى الدرس التالي: 4. الفئات والكائنات

للحصول على نظرة عامة على الدورة التدريبية، بما في ذلك روابط تؤدي إلى دروس تطبيقية أخرى حول الترميز، يُرجى الاطّلاع على "برنامج Kotlin التدريبي للمبرمجين: مرحبًا بك في الدورة التدريبية".