Try   HackMD

Roughly cover the materials in Chapters 4 and 5.

Why Functions

  • Consider the following code:
    ​​​​import java.util.Scanner
    
    ​​​​fun main() {
    ​​​​    val scanner = Scanner(System.`in`)
    ​​​​    print("What is your score? ")
    ​​​​    val yourScore = scanner.nextInt()
    ​​​​    val yourGPA = when (yourScore) {
    ​​​​        in 90..100 -> 4.3
    ​​​​        in 85..89 -> 4.0
    ​​​​        in 80..84 -> 3.7
    ​​​​        in 77..79 -> 3.3
    ​​​​        in 73..76 -> 3.0
    ​​​​        in 70..72 -> 2.7
    ​​​​        in 67..69 -> 2.3
    ​​​​        in 63..66 -> 2.0
    ​​​​        in 60..62 -> 1.7
    ​​​​        in 50..59 -> 1.0
    ​​​​        else -> 0.0
    ​​​​    }
    
    ​​​​    print("What is his score? ")
    ​​​​    val hisScore = scanner.nextInt()
    ​​​​    val hisGPA = when (hisScore) {
    ​​​​        in 90..100 -> 4.3
    ​​​​        in 85..89 -> 4.0
    ​​​​        in 80..84 -> 3.7
    ​​​​        in 77..79 -> 3.3
    ​​​​        in 73..76 -> 3.0
    ​​​​        in 70..72 -> 2.7
    ​​​​        in 67..69 -> 2.3
    ​​​​        in 63..66 -> 2.0
    ​​​​        in 60..62 -> 1.7
    ​​​​        in 50..59 -> 1.0
    ​​​​        else -> 0.0
    ​​​​    }
    
    ​​​​    when {
    ​​​​        yourGPA > hisGPA -> println("Your GPA is better than his.")
    ​​​​        yourGPA < hisGPA -> println("His GPA is better than yours.")
    ​​​​        else -> println("Your GPA is equal to his.")
    ​​​​    }
    ​​​​}
    
  • Can we simplify the main function for readability?
    • Reuse the when expression
  • Functions are reusable protion of code
  • With proper use, functions make your code more readable.

Extracting Code to Functions

  • import java.util.Scanner ​fun main() {val scanner = Scanner(System.`in`)print("What is your score? ")val yourScore = scanner.nextInt()val yourGPA = scoreToGPA(yourScore)print("What is his score? ")val hisScore = scanner.nextInt()val hisGPA = scoreToGPA(hisScore)when { ​ yourGPA > hisGPA -> println("Your GPA is better than his.") ​ yourGPA < hisGPA -> println("His GPA is better than yours.")else -> println("Your GPA is equal to his.")}}private fun scoreToGPA(score: Int): Double {val yourGPA = when (score) {in 90..100 -> 4.3in 85..89 -> 4.0in 80..84 -> 3.7in 77..79 -> 3.3in 73..76 -> 3.0in 70..72 -> 2.7in 67..69 -> 2.3in 63..66 -> 2.0in 60..62 -> 1.7in 50..59 -> 1.0else -> 0.0}return yourGPA ​}

Refactor to Function

  1. Select the part of code
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  2. Right click -> Refactor -> Function
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  3. Give proper names to function and parameters
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
  4. IntelliJ detects duplicates
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

Header, Body, Scope

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • private: Visibility modifier
    • private means "can only be seen in the same file".
    • public means "can be seen everywhere" and is the default. When you ignore the visibility modifier, it is equivalent to use public.
    • We shall discuss other visibility modifiers later.
  • fun: function declaration keyword
    • Like var and val for variables and values
  • scoreToGPA: function name
  • (score: Int): function parameters
    • Can be none: ()
      • Example: main
    • Can be multiple: (arg1: Type1, arg2: Type 2, arg3: Type3)
  • : Double: return data type
    • The type of the output data of the function

Body

  • Contain the reusable code.
  • Use return statement to end the function and pass the result to "caller".
  • Special case: if the return type is omitted in header or the return type is Unit
    • return does not need a value to pass to caller.
    • When the code runs to the end of the body, the function automatically returns.

Scope

  • The function:
    • Defined by visibility modifier
  • The variables and values in function body
    • Cannot be seen by other function (function bodies)
  • File-level variables and values
    • Defined outside of function
    • Can be seen by all function bodies in the file

Call a Function

  • functionName(arg1,...,argN)
    • Examples:
      • scoreToGPA(56)
        • After the execution of scoreToGPA, the return value will "replace" scoreToGPA(56) with 1.0 in the statement.
      • print("Please input an integer: ")
        • Often used as a statement
  • Member function:
    • Function associated with certain type of data
    • Example: Scanner.next, Scanner.nextInt, Scanner.nextDouble

Practice: Write Functions

  1. Modify the function isLeapYear
import java.util.Scanner

fun main() {
    val scanner = Scanner(System.`in`)
    val year = 2020 // scanner.nextInt()
    // 2020 is a leap year!
    println(if (isLeapYear(year)) "Yes" else "No")
}

private fun isLeapYear(year: Int): Boolean {
    return false
}
  1. Modify the function greet to print greeing messages with name.
import java.util.Scanner

fun main() {
    val scanner = Scanner(System.`in`)
    val name = "MZ" // scanner.next()
    // Should print "MZ san, konnichi wa."
    greet(name)
}

private fun greet(name: String): Unit {
    println("Mina san, konnichi wa.")
}
  1. Modify the function sumUp to compute the sum of elements in an IntProgression
import java.util.Scanner

fun main() {
    val scanner = Scanner(System.`in`)
    val first = 1 // scanner.nextInt()
    val last = 7 // scanner.nextInt()
    val gap = 3 // scanner.nextInt()
    // Should print 12
    println(sumUp(first..last step gap))
}

private fun sumUp(progression: IntProgression): Int {
    return 0
}
  1. Modify the function to compute the average of two Double values.
import java.util.Scanner

fun main() {
    val scanner = Scanner(System.`in`)
    val x = 113.0 // scanner.nextDouble()
    val y = 114.0 // scanner.nextDouble()
    // Should print 113.5
    println(average(x, y))
}

private fun average(x: Double, y: Double): Double {
    return 0.0
}
  1. Complete the function to compute the median of three Int values.
import java.util.Scanner

fun main() {
    val scanner = Scanner(System.`in`)
    val x = 113 // scanner.nextInt()
    val y = 222 // scanner.nextInt()
    val z = 87 // scanner.nextInt()
    // Should print 113
    println(medianOf(x, y, z))
}

private fun medianOf(a: Int, b: Int, c: Int): Int {
    return 0
}

Default Argument and Named Argument

private fun bless(country: String = "Taiwan", god: String = "Mazu") {
    println("$god bless $country")
}

fun main() {
    // call bless with default arguments
    // should print "Mazu bless Taiwan"
    bless()
    // call bless with positional arguments
    // should print "God bless America"
    bless("America", "God")
    // call bless with named arguments
    // should print "God bless America"
    bless(god = "God", country = "America")
    // country: default argument
    // god: named argument
    // should print "God bless Taiwan"
    bless(god = "God")
    // country: positional argument
    // god: default argument
    // should print "Mazu bless America"
    bless("America")
}

Single-Expression Functions

If the function body is simply

{
    return expression
}

then we can replace it with

= expression
  • Example 1
fun squareOf(n: Int): Int {
    return n * n
}

is equivalent to

fun squareOf(n: Int) = n * n // The return type can be derived.
  • Example 2
fun difference(x: Int, y: Int): Int {
    return if (x > y) x - y else y - x
}

is equivalent to

fun difference(x: Int, y: Int) = if (x > y) x - y else y - x
  • Special case
fun main() {
    println("hello")
}

is equivalent to

fun main() = println("hello")

Reserved Words and Backticks

  • Reserved words in Kotlin
  • val, var, fun, if, else, when, while, for,
  • They are invalid identifiers
  • Use backticks ` to escape.
  • Example: System.`in`

Anonymous Functions

  • Function defined with a name: named function

  • Function defined without a name: anonymous function

    • lambda
  • Define anonymous functions like data

    • With proper function type
    • With implicit return
    • With it or proper arguments
  • Examples

import java.util.Scanner

fun main() {
    val scoreToGPA: (Int) -> Double = { score ->
        when (score) {
            in 90..100 -> 4.3
            in 85..89 -> 4.0
            in 80..84 -> 3.7
            in 77..79 -> 3.3
            in 73..76 -> 3.0
            in 70..72 -> 2.7
            in 67..69 -> 2.3
            in 63..66 -> 2.0
            in 60..62 -> 1.7
            in 50..59 -> 1.0
            else -> 0.0
        }
    }

    val scanner = Scanner(System.`in`)
    print("What is your score? ")
    val yourScore = scanner.nextInt()
    val yourGPA = scoreToGPA(yourScore)

    print("What is his score? ")
    val hisScore = scanner.nextInt()
    val hisGPA = scoreToGPA(hisScore)

    when {
        yourGPA > hisGPA -> println("Your GPA is better than his.")
        yourGPA < hisGPA -> println("His GPA is better than yours.")
        else -> println("Your GPA is equal to his.")
    }
}

Function Type

  • Like data, functions have types.
  • fun funName(arg1: Type1,...,argN: TypeN): RetType
    • funName has type (Type1,...,TypeN) -> RetType
  • Unit Function: (Type1, ..., TypeN) -> Unit
  • Example:
    • main has type () -> Unit
    • scoreToGPA has type (Int) -> Double

Implicit Returns

  • The last expression in the function body of anonymous function is the return value
  • You may not use return in anonymous function.

Argument Name

  • When we define an anonymous function, we do not define the names of the arguments with their types.
  • Names are defined in the function body.
fun difference(x: Int, y: Int): Int {
    return if (x > y) x - y else y - x
}

is equivalent to

val difference: (Int, Int) -> Int = {
    x, y -> if (x > y) x - y else y - x
}

it Keyword

When the function has exactly one argument, then we can use it to simplify the code.

import java.util.Scanner

fun main() {
    val scoreToGPA: (Int) -> Double = { 
        when (it) {
            in 90..100 -> 4.3
            in 85..89 -> 4.0
            in 80..84 -> 3.7
            in 77..79 -> 3.3
            in 73..76 -> 3.0
            in 70..72 -> 2.7
            in 67..69 -> 2.3
            in 63..66 -> 2.0
            in 60..62 -> 1.7
            in 50..59 -> 1.0
            else -> 0.0
        }
    }

    val scanner = Scanner(System.`in`)
    print("What is your score? ")
    val yourScore = scanner.nextInt()
    val yourGPA = scoreToGPA(yourScore)

    print("What is his score? ")
    val hisScore = scanner.nextInt()
    val hisGPA = scoreToGPA(hisScore)

    when {
        yourGPA > hisGPA -> println("Your GPA is better than his.")
        yourGPA < hisGPA -> println("His GPA is better than yours.")
        else -> println("Your GPA is equal to his.")
    }
}

Type Inference Support

  • Specify the types of the arguments, then Kotlin can derive the type of the anonymous function.
val difference: (Int, Int) -> Int = {
    x, y -> if (x > y) x - y else y - x
}

is equivalent to

val difference = {
    x: Int, y: Int -> if (x > y) x - y else y - x
}

Function Reference

  • Since we have function types in Kotlin, we can use function types as arguments and return types.

  • String.count is a function that accepts a function as an argument. See the document.

  • Function Reference: use :: operator to obtain the "reference" of a named function.

fun isN(chr: Char): Boolean {
    return chr == 'N' || chr == 'n'
}

fun isC(chr: Char) = chr == 'C' || chr == 'c'

fun main() {
    val univ = "National Yang Ming Chiao Tung University"
    val total = univ.count()
    println("Total characters: $total")
    val numN = univ.count(::isN)
    println("Number of N's: $numN")
    val numC = univ.count(::isC)
    println("Number of C's: $numC")
    // val isT = {chr: Char -> chr == 'T' || chr == 't'}
    val isT: (Char) -> Boolean = {it == 'T' || it == 't'}
    val numT = univ.count(isT)
    println("Number of T's: $numT")
    // val numU = univ.count({it == 'U' || it == 'u'})
    // short hand example
    val numU = univ.count{it == 'U' || it == 'u'}
    println("Number of U's: $numU")
}
  • Short hand:
    • If the only argument is a function and we are going to pass an anonymous function, then we can ignore the brackets ( and ) to simplify the notation.
    • If the last argument is a function and we are going to pass an anonymous function, then we can omit the last argument and append the anonymous function.