מחנה קוטלין לסימולטורים 3: פונקציות

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

מבוא

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

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

דברים שחשוב לדעת

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

שיעור Lab זה מיועד למתכננים שיודעים שפה בעלת אובייקטים ורוצים לקבל מידע נוסף על קוטלין.

מה תלמדו

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

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

  • יש לעבוד עם REPL כדי לנסות קוד כלשהו.
  • עבודה עם IntelliJ IDEA ליצירת תוכניות Kotlin בסיסיות.

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

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

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

printHello()
⇒ Hello World

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

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

  1. פותחים את IntelliJ IDEA.
  2. בחלונית Project (פרויקט) מימין ב- IntelliJ IDEA מוצגת רשימה של הקבצים והתיקיות מהתיקייה לפרויקט. מאתרים את התיקייה src שמתחת לשלום קוטלין, ולוחצים עליה לחיצה ימנית. (אתם אמורים לקבל כבר את פרויקט שלום קוטלין ממעבדת הקוד הקודמת.)
  3. בוחרים באפשרות &gt חדש; קובץ Kotlin / כיתה.
  4. משאירים את הקובץ Kind כקובץ ונותן לקובץ את השם שלום.
  5. לוחצים על אישור.

עכשיו יש קובץ בתיקייה src שנקרא שלום.kt.

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

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

    יש להקליד או להדביק את הקוד הבא בקובץ שלום.kt:
fun main(args: Array<String>) {
    println("Hello, world!")
}

בדומה לפונקציה printHello() הקודמת, לפונקציה הזו אין הצהרה ב-return. כל פונקציה ב-Kotlin מחזירה משהו, גם אם לא צוינה דבר באופן מפורש. לכן, פונקציה כמו main() מחזירה פונקציה מסוג kotlin.Unit, שהיא דרך לומר ל-Kotlin' אין ערך.

  1. כדי להפעיל את התוכנית, לוחצים על המשולש הירוק שמימין לפונקציה main(). בוחרים באפשרות Run 'helloKt' מהתפריט.
  2. IntelliJ IDEA הידור התוכנית ומפעיל אותה. התוצאות מופיעות בחלונית יומן בחלק התחתון, כפי שמוצג למטה.

שלב 3: מעבירים ארגומנטים ל-primary()

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

  1. בוחרים באפשרות Run > עריכת תצורות. החלון Run/Debug Configuration ייפתח.
  2. מקלידים Kotlin! בשדה ארגומנטים של התוכנית.
  3. לוחצים על אישור.

שלב 4: משנים את הקוד כדי להשתמש בתבנית מחרוזת

תבנית מחרוזת מאפשרת להוסיף משתנה או ביטוי למחרוזת, והמחרוזת $ מציינת שחלק מהמחרוזת יהיה משתנה או ביטוי. הסוגריים המסולסלים {} מוסיפים מסגרת לביטוי, אם יש כאלה.

  1. בקטע שלום.kt, שנה את הודעת הפתיחה כדי להשתמש בארגומנט הראשון שהועבר לתוכנית, args[0], במקום "world".
fun main(args: Array<String>) {
    println("Hello, ${args[0]}")
}
  1. מריצים את התוכנית, והפלט כולל את הארגומנט שצוין.
⇒ Hello, Kotlin!

במשימה הזו אפשר להבין למה כמעט לכל דבר בקוטלין יש ערך, ולמה זה שימושי.

לחלק מהשפות האחרות יש הצהרות, שהן שורות קוד שאין להן ערך. בקוטלין, כמעט כל ביטוי הוא ביטוי ויש לו ערך – גם אם הערך הזה הוא kotlin.Unit.

  1. ב-שלום.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: יצירת פונקציות מסוימות

בשלב זה מלמדים חלק מהתכנים שלמדתם ויוצרים פונקציות עם סוגים שונים. אפשר להחליף את התוכן של שלום.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() עד 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 דומה ל-switch בשפות תכנות אחרות, אבל when מפסיקה באופן אוטומטי בסוף כל סניף. הוא גם מוודא שהקוד מכסה את כל הסניפים אם אתם בודקים ערך enum.

  1. ב-שלום.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. ב-שלום.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. ב-שלום.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. כשפונקציה מחזירה את התוצאות של ביטוי יחיד, אפשר לציין את גוף הפונקציה אחרי סמל =, להשמיט את הסוגריים המסולסלים {} ולהשמיט את return.

  1. ב-שלום.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 {
    ...

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

שלב 1: יצירת מסנן

  1. בקטע שלום.kt, מגדירים רשימה של קישוטי אקווריום ברמה העליונה באמצעות listOf(). אפשר להחליף את התוכן ב-שלום.kt.
val decorations = listOf ("rock", "pagoda", "plastic plant", "alligator", "flowerpot")
  1. יש ליצור פונקציה חדשה מסוג main() עם שורה, כדי להדפיס רק את האותות שמתחילים באות &33;p'. הקוד של תנאי המסנן נמצא בסוגריים מסולסלים {}, והמאפיין it מתייחס לכל פריט כאשר המסנן עובר בלופ. אם הביטוי מחזיר את true, הפריט נכלל.
fun main() {
    println( decorations.filter {it[0] == 'p'})
}
  1. מריצים את התוכנית ומשתמשים בפלט הבא בחלון הפעלה:
⇒ [pagoda, plastic plant]

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

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

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

  1. בקטע שלום.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 הוא ביטוי שיוצר פונקציה. אבל במקום הצהרה על פונקציה בעלת שם, אתם מצהירים על פונקציה שאין לה שם. אחד היתרונות של הקטע הזה הוא שאפשר להעביר את הביטוי lambda כנתונים. בשפות אחרות, למורים יש שמות פונקציות אנונימיות, ליטרות של פונקציות או שמות דומים.

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

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

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

שלב 1: מידע על למדות

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

    אפשר להשתמש ב-REPL (כלים > Ktlin > Kotlin REPL), ולנסות את הקוד הבא:
var dirtyLevel = 20
val waterFilter = { dirty : Int -> dirty / 2}
println(waterFilter(dirtyLevel))
⇒ 10

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

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

כך כתוב הקוד:

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

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

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

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

  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

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

  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 > עריכת הגדרות.
  • כמעט לכל דבר בקוטלין יש ערך. אפשר להשתמש בעובדה זו כדי להפוך את הקוד שלך לתמציתי יותר על ידי שימוש בערך של 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 היא נקודת ההתחלה הטובה ביותר.

מדריכים בקוטלין

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

קורס של Udacity

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

IntelliJ IDEA

ניתן למצוא תיעוד עבור IntelliJ IDEA באתר JetBrains.

בקטע הזה מפורטות מטלות שיעורי בית אפשריות לתלמידים שעובדים עם קוד Lab הזה, במסגרת קורס בהדרכת מורה. למורה יש אפשרות לבצע את הפעולות הבאות:

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

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

אם אתם עובדים בעצמכם על שיעור הקוד הזה, אתם מוזמנים להשתמש במטלות שיעורי הבית האלה כדי לבחון את הידע שלכם.

מענה על השאלות האלה

שאלה 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 Bootcamp for Programmers: ברוך הבא לקורס."