Kotlin Bootcamp for Programmers 3: Functions

ה-codelab הזה הוא חלק מקורס Kotlin Bootcamp for Programmers. כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר. אם יש לכם ידע בנושא, יכול להיות שתוכלו לדלג על חלק מהקטעים. הקורס הזה מיועד למתכנתים שמכירים שפה מונחית-אובייקטים ורוצים ללמוד Kotlin.

מבוא

ב-codelab הזה תיצרו תוכנית Kotlin ותלמדו על פונקציות ב-Kotlin, כולל ערכי ברירת מחדל לפרמטרים, מסננים, פונקציות למדה ופונקציות קומפקטיות.

במקום ליצור אפליקציה לדוגמה אחת, השיעורים בקורס הזה נועדו להרחיב את הידע שלכם, אבל הם גם עצמאיים למחצה, כך שתוכלו לדלג על חלקים שאתם מכירים. כדי לקשור בין הדוגמאות, רבות מהן מתבססות על נושא האקווריום. אם אתם רוצים לראות את הסיפור המלא של האקווריום, כדאי לעיין בקורס Kotlin Bootcamp for Programmers ב-Udacity.

מה שכדאי לדעת

  • היסודות של שפת תכנות מודרנית, מונחית-עצמים ועם הקלדה סטטית
  • איך לתכנת עם מחלקות, שיטות וטיפול בחריגים בשפה אחת לפחות
  • איך עובדים עם REPL (Read-Eval-Print Loop) של Kotlin ב-IntelliJ IDEA
  • היסודות של Kotlin, כולל סוגים, אופרטורים ולולאות

ה-codelab הזה מיועד למתכנתים שמכירים שפה מונחית-עצמים ורוצים ללמוד עוד על Kotlin.

מה תלמדו

  • איך יוצרים תוכנית עם פונקציה וארגומנטים של main() ב-IntelliJ IDEA
  • איך משתמשים בערכי ברירת מחדל ובפונקציות קומפקטיות
  • איך להחיל מסננים על רשימות
  • איך יוצרים פונקציות למבדה בסיסיות ופונקציות מסדר גבוה

הפעולות שתבצעו:

  • אפשר להשתמש ב-REPL כדי לנסות קוד.
  • שימוש ב-IntelliJ IDEA כדי ליצור תוכניות בסיסיות ב-Kotlin.

במשימה הזו תיצרו תוכנית Kotlin ותלמדו על הפונקציה main() ועל האופן שבו מעבירים ארגומנטים לתוכנית משורת הפקודה.

יכול להיות שאתם זוכרים את הפונקציה printHello() שהזנתם ב-REPL ב-codelab קודם:

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

printHello()
⇒ Hello World

מגדירים פונקציות באמצעות מילת המפתח fun, ואחריה שם הפונקציה. כמו בשפות תכנות אחרות, הסוגריים () משמשים לארגומנטים של פונקציות, אם יש כאלה. הסוגריים המסולסלים {} מסמנים את הקוד של הפונקציה. אין סוג החזרה לפונקציה הזו, כי היא לא מחזירה כלום.

שלב 1: יוצרים קובץ Kotlin

  1. פותחים את IntelliJ IDEA.
  2. בחלונית Project בצד ימין ב-IntelliJ IDEA מוצגת רשימה של קבצים ותיקיות בפרויקט. מחפשים את התיקייה src ולוחצים עליה לחיצה ימנית. (אמור להיות לכם כבר פרויקט Hello Kotlin מה-codelab הקודם).
  3. בוחרים באפשרות New > Kotlin File / Class (חדש > קובץ או מחלקה של Kotlin).
  4. משאירים את Kind (סוג) כ-File (קובץ) ונותנים לקובץ את השם 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(). בתפריט, בוחרים באפשרות Run 'HelloKt' (הפעלת HelloKt).
  2. ‫IntelliJ IDEA יקמפל את התוכנית ויריץ אותה. התוצאות יופיעו בחלונית יומן בתחתית, כמו שמוצג בהמשך.

שלב 3: העברת ארגומנטים אל main()

מכיוון שאתם מריצים את התוכנית מ-IntelliJ IDEA ולא משורת הפקודה, אתם צריכים לציין את הארגומנטים של התוכנית בצורה קצת שונה.

  1. בוחרים באפשרות הפעלה > עריכת הגדרות. ייפתח החלון Run/Debug Configurations (הגדרות הרצה/ניפוי באגים).
  2. מקלידים Kotlin! בשדה Program arguments (ארגומנטים של התוכנית).
  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 מפסיקה אוטומטית בסוף כל ענף. בנוסף, אם בודקים enum, הקוד יכסה את כל הענפים.

  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. מריצים את התוכנית, ובחלון Run מוצג הפלט הבא:
⇒ [pagoda, plastic plant]

שלב 2: השוואה בין מסננים שמופעלים באופן מיידי לבין מסננים שמופעלים באופן מושהה

אם אתם מכירים מסננים בשפות אחרות, יכול להיות שתהיתם אם המסננים ב-Kotlin הם eager או lazy. האם רשימת התוצאות נוצרת באופן מיידי או כשניגשים לרשימה? ב-Kotlin, זה קורה בכל דרך שרוצים. כברירת מחדל, filter הוא חמדן, ובכל פעם שמשתמשים במסנן, נוצרת רשימה.

כדי להגדיר את המסנן כעצלן, אפשר להשתמש ב-Sequence, שהוא אוסף שיכול לבדוק רק פריט אחד בכל פעם, החל מההתחלה ועד הסוף. למזלנו, זה בדיוק ה-API שנדרש לסינון עצלני.

  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]

במשימה הזו תקבלו מבוא ל-lambdas ולפונקציות מסדר גבוה ב-Kotlin.

Lambdas

בנוסף לפונקציות בעלות שם רגילות, Kotlin תומכת ב-lambdas. למדה היא ביטוי שיוצר פונקציה. אבל במקום להצהיר על פונקציה בעלת שם, אתם מצהירים על פונקציה ללא שם. אחד היתרונות של השיטה הזו הוא שאפשר להעביר את ביטוי ה-lambda כנתונים. בשפות אחרות, פונקציות למדה נקראות פונקציות אנונימיות, פונקציות מילוליות או שמות דומים.

פונקציות מסדר גבוה

אפשר ליצור פונקציה מסדר גבוה יותר על ידי העברת פונקציית lambda לפונקציה אחרת. במשימה הקודמת יצרתם פונקציה מסדר גבוה בשם filter. העברת את ביטוי ה-lambda הבא אל filter כתנאי לבדיקה:
{it[0] == 'p'}

באופן דומה, map היא פונקציה מסדר גבוה, והלמדה שהעברתם אליה הייתה הטרנספורמציה להחלה.

שלב 1: מידע על פונקציות למדא

  1. בדומה לפונקציות בעלות שם, לפונקציות למדה יכולים להיות פרמטרים. בפונקציות למדה, הפרמטרים (והסוגים שלהם, אם צריך) מופיעים משמאל למה שנקרא חץ פונקציה ->. הקוד להרצה מופיע משמאל לחץ הפונקציה. אחרי שמקצים את ה-lambda למשתנה, אפשר לקרוא לו בדיוק כמו לפונקציה.

    אפשר לנסות את הקוד הזה באמצעות REPL‏ (Tools > Kotlin > Kotlin REPL):
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10

בדוגמה הזו, פונקציית ה-lambda מקבלת את הארגומנט Int שנקרא dirty ומחזירה את הערך dirty / 2. (כי הסינון מסיר את הלכלוך).

  1. התחביר של Kotlin לסוגי פונקציות קשור קשר הדוק לתחביר של lambdas. משתמשים בתחביר הזה כדי להצהיר בצורה נקייה על משתנה שמכיל פונקציה:
val waterFilter: (Int) -> Int = { dirty -> dirty / 2 }

הנה מה שכתוב בקוד:

  • יוצרים משתנה בשם waterFilter.
  • waterFilter יכולה להיות כל פונקציה שמקבלת Int ומחזירה Int.
  • מקצים את פונקציית ה-lambda ל-waterFilter.
  • פונקציית ה-lambda מחזירה את הערך של הארגומנט dirty חלקי 2.

שימו לב שכבר לא צריך לציין את הסוג של ארגומנט ה-lambda. הסוג מחושב על ידי היסק סוגים.

שלב 2: יצירת פונקציה מסדר גבוה

עד עכשיו, הדוגמאות ל-lambdas נראות בעיקר כמו פונקציות. היתרון האמיתי של פונקציות למדא הוא השימוש בהן ליצירת פונקציות מסדר גבוה, שבהן הארגומנט של פונקציה אחת הוא פונקציה אחרת.

  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

הפונקציה שמעבירים לא חייבת להיות למדה, היא יכולה להיות פונקציה רגילה בעלת שם. כדי לציין את הארגומנט כפונקציה רגילה, משתמשים באופרטור ::. כך מערכת 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() ב-Run > Edit Configurations (הרצה > עריכת הגדרות).
  • כמעט לכל דבר ב-Kotlin יש ערך. אפשר להשתמש בעובדה הזו כדי לקצר את הקוד באמצעות הערך של if או when כביטוי או כערך מוחזר.
  • ארגומנטים שמוגדרים כברירת מחדל מייתרים את הצורך בכמה גרסאות של פונקציה או שיטה. לדוגמה:
    fun swim(speed: String = "fast") { ... }
  • פונקציות קומפקטיות, או פונקציות עם ביטוי יחיד, יכולות לשפר את קריאות הקוד. לדוגמה:
    fun isTooHot(temperature: Int) = temperature > 30
  • למדתם כמה דברים בסיסיים על מסננים, שמשתמשים בביטויי למבדה. לדוגמה:
    val beginsWithP = decorations.filter { it [0] == 'p' }
  • ביטוי למדה הוא ביטוי שיוצר פונקציה ללא שם. ביטויי Lambda מוגדרים בין סוגריים מסולסלים {}.
  • בפונקציה מסדר גבוה, מעבירים פונקציה כמו ביטוי למבדה לפונקציה אחרת כנתונים. לדוגמה:
    dirtyLevel = updateDirty(dirtyLevel) { dirtyLevel -> dirtyLevel + 23}

יש הרבה מידע בשיעור הזה, במיוחד אם אתם חדשים בנושא של פונקציות למדה. באחד השיעורים הבאים נחזור לנושא של פונקציות למבדה ופונקציות מסדר גבוה.

תיעוד של Kotlin

אם אתם רוצים לקבל מידע נוסף על נושא כלשהו בקורס הזה, או אם נתקעתם, https://kotlinlang.org הוא המקום הכי טוב להתחיל בו.

מדריכים ל-Kotlin

באתר https://try.kotlinlang.org יש הדרכות מפורטות שנקראות Kotlin Koans, מפרש מבוסס-אינטרנט וסט מלא של מסמכי עזר עם דוגמאות.

קורס של Udacity

כדי לצפות בקורס של Udacity בנושא הזה, אפשר לעבור אל Kotlin Bootcamp for Programmers.

IntelliJ IDEA

מסמכי התיעוד של IntelliJ IDEA זמינים באתר JetBrains.

בקטע הזה מפורטות אפשרויות למשימות ביתיות לתלמידים שעובדים על ה-Codelab הזה כחלק מקורס בהנחיית מדריך. המורה צריך:

  • אם צריך, מקצים שיעורי בית.
  • להסביר לתלמידים איך להגיש מטלות.
  • בודקים את שיעורי הבית.

אנשי ההוראה יכולים להשתמש בהצעות האלה כמה שרוצים, ומומלץ להם להקצות כל שיעורי בית אחרים שהם חושבים שמתאימים.

אם אתם עובדים על ה-codelab הזה לבד, אתם יכולים להשתמש במשימות האלה כדי לבדוק את הידע שלכם.

עונים על השאלות הבאות

שאלה 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. מחלקות ואובייקטים

סקירה כללית של הקורס, כולל קישורים ל-Codelabs אחרים, זמינה במאמר "Kotlin Bootcamp for Programmers: Welcome to the course".