Ronnie Atuhaire's Blog 😎

Ronnie Atuhaire's Blog 😎

Kotlin Functions

Kotlin Functions

Subscribe to my newsletter and never miss my upcoming articles

Hello there👋, welcome back to my blog! I don't take this for granted! I truly appreciate you being here the fact that there are millions of resources out there. image.png

So, today we shall learn one of the most fundamental parts of programming languages especially functional and object-oriented languages.

We learnt expressions and control flow statements in our last tutorial in the series . So sit tight and we get to learn functions in Kotlin.

🌟 What are Functions ❔

A function is a block of organized, reusable code that is used to perform a single, related action. image.png Functions (also called 'procedures' in some programming languages and 'methods' in most object-oriented programming languages) are a set of instructions bundled together to achieve a specific outcome.

🎈Function Types

There are two types of functions depending on whether it is available in the standard library or defined by the user.
-> Standard library functions
-> User-defined functions

In this tutorial, we shall mainly use user-defined functions. We may not need to use library functions like Math.sqrt(number.toDouble()) etc

🎈 Declarations

Kotlin functions are declared using the fun keyword whereas def for Python functions with parentheses ( ). Kotlin Functions are first-class citizens. It means that functions can be assigned to the variables, passed as arguments or returned from another function.
Kotlin ➖ fun myFunction()
Python ➖ def myFunction()

🎈 Stracture

image.png Image By: Pro Android Dev

💨 Function Name -> This is the name of the descriptive function.
💨 Return Type -> This is the expected outcome of the function.
💨 Parameters -> A special kind of variable used in a subroutine.
💨 Function Body -> This defines the scope of the defined function.

In Python, function scope is bounded by indentation whereas { } for Kotlin.
💠Analogous Examples: Add Functions

# Python Add Function
def add(a,b):
    return a + b
// Kotlin Add Function
fun add(a:Int, b:Int): Int{
    return a + b
}

🎈 Function Calls:

To call the above functions, in python we simply pass the arguments required but in Kotlin, we require a top-level declaration of the main function. So, you will see main() in every Kotlin program you might come across.

💠Analogous Examples: Function Calling

# Python calling -> For Terminal Output
print(add(30,50))
// Kotlin Calling
fun main (){
    println(add(30,50))
}

You can also use variable assignments and print out the results:
💨Python ->result = add(30,50)
💨Kotlin ->var result = add(30,50)

You can call member functions using dot notation: Applies to Python & Kotlin
Like this -> add().read()

🎈 Parameters

Function parameters are defined using Pascal notation - name: type. Parameters are separated using commas, and unlike Python, each Kotlin parameter must be explicitly typed:
fun powerOf(number: Int, exponent: Int): Int { /*...*/ }

You can use a trailing comma when you declare function parameters:

fun powerOf(
    number: Int,
    exponent: Int, // trailing comma
) { /*...*/ }

🎈 Default arguments

Just like in Python, Kotlin function parameters can have default values, which are used when you skip the corresponding argument. This reduces the number of overloads:

fun read(
    b: ByteArray,
    off: Int = 0,
    len: Int = b.size, 
) { /*...*/ }

A default value is defined using = after the type. This applies to both languages!

If a default parameter precedes a parameter with no default value, the default value can only be used by calling the function with named arguments:

fun foo(
    bar: Int = 0,
    baz: Int,
) { /*...*/ }

foo(baz = 1) // The default value bar = 0 is used

If the last argument after default parameters is a lambda, you can pass it either as a named argument or outside the parentheses:

fun foo(
    bar: Int = 0,
    baz: Int = 1,
    qux: () -> Unit, // returns None type like Python
) { /*...*/ }
foo(1) { println("hello") }     // Uses the default value baz = 1
foo(qux = { println("hello") }) // Uses both default values bar = 0 and baz = 1
foo { println("hello") }        // Uses both default values bar = 0 and b

🎈 Named Arguments

A named argument is an argument in which we define the name of an argument in the function call. The name defined to the argument of function call checks the name in the function definition and assign it to it.

When you use named arguments in a function call, you can freely change the order they are listed in, and if you want to use their default values, you can just leave these arguments out altogether.

Kotlin Named Argument Example:

fun main() {  
    blogger(letter = 'K')  
}  
fun blogger(blog: String = "Hashnode", num:Int= 7, letter: Char ='x'){  
    print("This is my $num th installation in this series at $blog.. $letter")  
}

As you can see, we may skip other arguments. This is very handy and exactly implemented like Python.

🎈 vararg

You can pass a variable number of arguments (vararg ) with names using the spread operator:

fun foo(vararg strings: String) { /*...*/ }

foo(strings = *arrayOf("a", "b", "c"))
// the * helps in unpacking

🎈 Overloading

image.png In Python, function names must be unique within a module or a class. In Kotlin, we can overload functions: there can be multiple declarations of functions that have the same name.

Overloaded functions must be distinguishable from each other through their parameter lists. (The types of the parameter list, together with the return type, is known as a function’s signature, but the return type cannot be used to disambiguate overloaded functions.)

For example, we can have both of these functions in the same file:

fun square(number: Int) = number * number
fun square(number: Double) = number * number

At the call sites, which function to use is determined from the type of the arguments:

square(4)    // Calls the first function; result is 16 (Int)
square(3.14) // Calls the second function; result is 9.8596 (Double)

While this example happened to use the same expression, that is not necessary - overloaded functions can do completely different things if need be (although your code can get confusing if you make functions that have very different behaviour be overloads of each other).

🎈 Unit-returning functions

If a function does not return a useful value, its return type is Unit. Unit is a type with only one value - Unit. This value does not have to be returned explicitly:

fun printHello(name: String?): Unit {
    if (name != null)
        println("Hello $name")
    else
        println("Hi there!")
    // `return Unit` or `return` is optional
}

The Unit return type declaration is also optional. The above code is equivalent to:

fun printHello(name: String?) { ... }

🎈 Single-expression functions

When a function returns a single expression, the curly braces can be omitted and the body is specified after a = symbol:
fun double(x: Int): Int = x * 2

Explicitly declaring the return type is optional when this can be inferred by the compiler:
fun double(x: Int) = x * 2

🎈 Infix Functions

Functions marked with the infix keyword can also be called using the infix notation (omitting the dot and the parentheses for the call). Infix functions must meet the following requirements:

🚩 The function is defined using the infix keyword
🚩 They must be member functions or extension functions.
🚩 They must have a single parameter.
🚩 The parameter must not accept a variable number of arguments and must have no default value.

These functions can be called without using the period and brackets. These are called infix methods, and their use can result in code that looks much more like a natural language.

This is most commonly seen in the inline Map definition:

map(
  1 to "one",
  2 to "two",
  3 to "three"
)

Kotlin program of creating a square function with infix notation –

class math {
    // user defined infix member function
    infix fun square(n : Int): Int{
        val num = n * n
        return num
    }
}
fun main() {
val m = math()
    // call using infix notation
    val result = m square 30
    print("The square of a number is: "+result) //-> 900
}

As you can see above, we just called the function like this: m square 3 !

🎈 Lambda Functions

Lambda is a function that has no name. Lambda is defined with curly braces {} which takes variable as a parameter (if any) and body of the function.

Syntax of Lambda expression –
val lambda_name : Data_type = { argument_List -> code_body }
The body of the function is written after the variable (if any) followed by -> operator.

Examples:

// Kotlin Code
// with type annotation in lambda expression
val sum1 = { a: Int, b: Int -> a + b }

// without type annotation in lambda expression
val sum2:(Int,Int)-> Int = { a , b -> a + b}

fun main() {
    val result1 = sum1(2,3)
    val result2 = sum2(3,4)
    println("The sum of two numbers is: $result1")
    println("The sum of two numbers is: $result2")

    // directly print the return value of lambda
    // without storing in a variable.
    println(sum1(5,7))
}
#  Python Analogous Code
# No Type Inferences in Python

sum1 = lambda a, b : a + b
sum2 = lambda a, b : a + b

def main():
    result1 = sum1(2,3)
    result2 = sum2(3,4)

    print(f"The sum of two numbers is: {result1}")
    print(f"The sum of two numbers is: {result2}")
    print(sum1(5,7))

print(main())

Use lambda functions when an anonymous function is required for a short period of time.
Read More about lambda functions here >>>>>

🎈 Recursive Functions

image.png A Recursive function is a function which calls itself continuously. This technique is called recursion.

Let's see an example of a recursion function calculating factorial of number.

fun main() {  
    val number = 5  
    val result: Long  
    result = factorial(number)  
    println("Factorial of $number = $result")  
}  

fun factorial(n: Int): Long {  
    return if(n == 1){  
          n.toLong()  
    }  
    else{  
        n*factorial(n-1)  
    }  
}
// Output:
Factorial of 5 = 120

Working process of above factorial example

factorial(5)   
   factorial(4)   
      factorial(3)   
         factorial(2)   
            factorial(1)   
               return 1   
            return 2*1 = 2   
         return 3*2 = 6   
      return 4*6 = 24   
   return 5*24 = 120

🎈 Tail Recursion

Tail recursion is a recursion that performs the calculation first, then makes the recursive call. The result of the current step is passed into the next recursive call.

Tail recursion follows one rule for implementation. This rule is as follow:

The recursive call must be the last call of the method. To declare a recursion as tail recursion we need to use tailrec modifier before the recursive function.

The above factorial example using tail recursive:

fun main() {  
    val number = 4  
    val result: Long  
    result = factorial(number)  
    println("Factorial of $number = $result")  
}  

tailrec fun factorial(n: Int, run: Int = 1): Long {  
    return if (n == 1){  
        run.toLong()  
    } else {  
        factorial(n-1, run*n)  
    }  
}

Let's see another example of calculating the sum of the nth (100000 larger number) using tail recursion.

fun main() {  
    var number = 100000.toLong()  
    var result = recursiveSum(number)  
    println("The sum of up to $number number = $result")  
}  
tailrec fun recursiveSum(n: Long, semiResult: Long = 0) : Long {  
    return if (n <= 0) {  
        semiResult  
    } else {  
        recursiveSum( (n - 1), n+semiResult)  
    }  
}

🎈 Generic functions

According to Geeks for Geeks, Generics are the powerful features that allow us to define classes, methods and properties which are accessible using different data types while keeping a check of the compile-time type safety.

Functions can have generic parameters, which are specified using angle brackets before the function name:

fun <T> singletonList(item: T): List<T> { /*...*/ }

As with Java, Kotlin’s generics are erased at runtime. That is, an instance of a generic class doesn’t preserve its type parameters at runtime. However, one does not need any special syntax to support generics in Python as it uses duck typing.

For example, if we create a Set and put a few strings into it, at runtime we’re only able to see it as a Set.

Let’s create two Sets with two different type parameters:

val books: Set<String> = setOf("1984", "Blogging at Hashnode is fun")
val primes: Set<Int> = setOf(2, 3, 11)

Generic class syntax is defined as follows:

class MyClass<T>( //......: T) {
  // Body .....
}

To create an instance of such a class, we need to provide the type arguments:

// String Type
val my : MyClass<String> = Myclass<String>("Consider Subscribing To My Blog")
// Int Type
val my : MyClass<String> = Myclass<Int>(80)

For more information on generic functions, see Official Generics Docs.

🎈 Higher-Order functions

High order function (Higher level function) is a function that accepts a function as a parameter or returns a function or can do both.

This means, instead of passing Int, String, or other types as a parameter in a function we can pass a function as a parameter in another function.

Try:

fun sayHello(name: String){
    println("In sayHello() function")
    println("Say hello to $name")
}

fun higherOrderFunction(functionName: (name: String)-> Unit, name: String){
    println("In higher order function")
    println("Calling sayHello() function...")
    functionName(name)
}

fun main() {
    higherOrderFunction(::sayHello, "Hahnoder")
}

Explained:
♨ The sayHello() function simply prints two strings.

♨ The higherOrderFunction() function takes two arguments. The first argument is a function and the second is a string. To accept a function as an argument, we need to define its definition in the form of lambda expression in the argument. Let us see its subparts:

♨ The name given to function taken as an argument is functionName. So we'll call the function accepted ( i.e. sayHello()) with functionName() now onwards.

♨ Next we added (name: String) which reflects the arguments of sayHello() function.

♨ After -> we mentioned the return type of sayHello() function.

♨ Inside higherOrderFunction() function we call functionName() (which is sayHello()) and passed name as the argument.

♨ To pass a function as an argument we use :: operator. We called higherOrderFunction() function from main() and passed sayHello() as an argument.

🎈Extension Functions

An extension function is a member function of a class that is defined outside the class. Extensions are resolved statically and can also be defined with the class type that is nullable. If a class contains a companion object, then we can also define extension functions and properties for the companion object.

fun String.removeFirstLastChar(): String = this.substring(1, this.length - 1)
println("Kotlin".removeFirstLastChar()) // => otli

🎈 Inline Functions

Using higher-order functions imposes certain runtime penalties: each function is an object, and it captures a closure. A closure means those variables that are accessed in the body of the function. Memory allocations (both for function objects and classes) and virtual calls introduce runtime overhead.

But it appears that in many cases this kind of overhead can be eliminated by inlining the lambda expressions.

fun higherfunc( str : String, mycall :(String)-> Unit) {

    // inovkes the print() by passing the string str
    mycall(str)
}

// main function
fun main() {
    print("Ronnie's Blog: ")
    higherfunc("A great Blog for Kotlin and Python learners",::print)
}

Read More about Inline functions at Official Docs & Geeks for Geeks

🎈 Function scope

image.png Kotlin functions can be declared at the top level in a file, meaning you do not need to create a class to hold a function, which you are required to do in languages such as Java, C#, and Scala.

In addition to top-level functions, Kotlin functions can also be declared locally as member functions and extension functions.

🎈 Function vs. Method

image.png Function and method are two commonly confused words. While every method is a function, not every function is a method.

A function returns a value, and a method is a function associated to an object. Function is a more general term, and all methods are also functions.

🎈 Parameters Vs Arguements

image.png According to MDN Web Docs, function parameters are the names listed in the function's definition. Function arguments are the real values passed to the function.

Parameters are initialized to the values of the arguments supplied.

⭐Conclusion

Yeah, that was one hell of a roller-coaster. I hope you learn something. If you are to forget everything I have shared here, just remember that Kotlin functions are fun.

If you liked this tutorial, kindly consider subscribing and following me here.

Ronnie Atuhaire 😎

 
Share this