Tuesday, September 12, 2017

Kotlin From Scratch: More Fun With Functions

Kotlin From Scratch: More Fun With Functions

Kotlin is a modern programming language that compiles to Java bytecode. It is free and open source, and promises to make coding for Android even more fun.  

In the previous article, you learned about packages and basic functions in Kotlin. Functions are at the heart of Kotlin, so in this post we'll look more closely at them. We'll be exploring the following kinds of functions in Kotlin:

  • top-level functions
  • lambda expressions or function literals
  • anonymous functions
  • local or nested functions
  • infix functions
  • member functions

You'll be amazed at all the cool things you can do with functions in Kotlin!

1. Top-Level Functions

Top-level functions are functions inside a Kotlin package that are defined outside of any class, object, or interface. This means that they are functions you call directly, without the need to create any object or call any class. 

If you're a Java coder, you know that we typically create utility static methods inside helper classes. These helper classes don't really do anything—they don't have any state or instance methods, and they just act as a container for the static methods. A typical example is the Collections class in the java.util package and its static methods. 

Top-level functions in Kotlin can be used as a replacement for the static utility methods inside helper classes we code in Java. Let's look at how to define a top-level function in Kotlin. 

In the code above, we defined a package com.chikekotlin.projectx.utils inside a file called UserUtils.kt and also defined a top-level utility function called checkUserStatus() inside this same package and file. For brevity's sake, this very simple function returns the string "online". 

The next thing we'll do is to use this utility function in another package or file.

In the preceding code, we imported the function into another package and then executed it! As you can see, we don't have to create an object or reference a class to call this function.

Java Interoperability

Given that Java doesn't support top-level functions, the Kotlin compiler behind the scenes will create a Java class, and the individual top-level functions will be converted to static methods. In our own case, the Java class generated was UserUtilsKt with a static method checkUserStatus()

This means that Java callers can simply call the method by referencing its generated class, just like for any other static method.

Note that we can change the Java class name that the Kotlin compiler generates by using the @JvmName annotation.

In the code above, we applied the @JvmName annotation and specified a class name UserUtils for the generated file. Note also that this annotation is placed at the beginning of the Kotlin file, before the package definition. 

It can be referenced from Java like this:

2. Lambda Expressions

Lambda expressions (or function literals) are also not bound to any entity such as a class, object, or interface. They can be passed as arguments to other functions called higher-order functions (we'll discuss these more in the next post). A lambda expression represents just the block of a function, and using them reduces the noise in our code. 

If you're a Java coder, you know that Java 8 and above provides support for lambda expressions. To use lambda expressions in a project that supports earlier Java versions such as Java 7, 6, or 5, we can use the popular Retrolambda library

One of the awesome things about Kotlin is that lambda expressions are supported out of the box. Because lambda is not supported in Java 6 or 7, for Kotlin to interoperate with it, Kotlin creates a Java anonymous class behind the scene. But note that creating a lambda expression in Kotlin is quite different than it is in Java.

Here are the characteristics of a lambda expression in Kotlin:

  • It must be surrounded by curly braces {}.
  • It doesn't have the fun keyword. 
  • There is no access modifier (private, public or protected) because it doesn't belong to any class, object, or interface.
  • It has no function name. In other words, it's anonymous. 
  • No return type is specified because it will be inferred by the compiler.
  • Parameters are not surrounded by parentheses ()

And, what's more, we can assign a lambda expression to a variable and then execute it. 

Creating Lambda Expressions

Let's now see some examples of lambda expressions. In the code below, we created a lambda expression without any parameters and assigned it a variable message. We then executed the lambda expression by calling message()

Let's also see how to include parameters in a lambda expression. 

In the code above, we created a lambda expression with the parameter myString, along with the parameter type String. As you can see, in front of the parameter type, there is an arrow: this refers to the lambda body. In other words, this arrow separates the parameter list from the lambda body. To make it more concise, we can completely ignore the parameter type (already inferred by the compiler). 

To have multiple parameters, we just separate them with a comma. And remember, we don't wrap the parameter list in parentheses like in Java. 

However, note that if the parameter types can't be inferred, they must be specified explicitly (as in this example), otherwise the code won't compile.

Passing Lambdas to Functions

We can pass lambda expressions as parameters to functions: these are called "higher-order functions", because they are functions of functions. These kinds of functions can accept a lambda or an anonymous function as parameter: for example, the last() collection function. 

In the code below, we passed in a lambda expression to the last() function. (If you want a refresher on collections in Kotlin, visit the third tutorial in this series) As the name says, it returns the last element in the list.  last() accepts a lambda expression as a parameter, and this expression in turn takes one argument of type String. Its function body serves as a predicate to search within a subset of elements in the collection. That means that the lambda expression will decide which elements of the collection will be considered when looking for the last one.

Let's see how to make that last line of code above more readable.

The Kotlin compiler allows us to remove the function parentheses if the last argument in the function is a lambda expression. As you can observe in the code above, we were allowed to do this because the last and only argument passed to the last() function is a lambda expression. 

Furthermore, we can make it more concise by removing the parameter type.

We don't need to specify the parameter type explicitly, because the parameter type is always the same as the collection element type. In the code above, we're calling last on a list collection of String objects, so the Kotlin compiler is smart enough to know that the parameter will also be a String type. 

The it Argument Name

We can even simplify the lambda expression further again by replacing the lambda expression argument with the auto-generated default argument name it.

The it argument name was auto-generated because last can accept a lambda expression or an anonymous function (we'll get to that shortly) with only one argument, and its type can be inferred by the compiler.  

Local Return in Lambda Expressions

Let's start with an example. In the code below, we pass a lambda expression to the foreach() function invoked on the intList collection. This function will loop through the collection and execute the lambda on each element in the list. If any element is divisible by 2, it will stop and return from the lambda. 

Running the above code might not have given you the result you might have expected. This is because that return statement won't return from the lambda but instead from the containing function surroundingFunction()! This means that the last code statement in the surroundingFunction() won't execute. 

To fix this problem, we need to tell it explicitly which function to return from by using a label or name tag. 

In the updated code above, we specified the default tag @forEach immediately after the return keyword inside the lambda. We have now instructed the compiler to return from the lambda instead of the containing function surroundingFunction(). Now the last statement of surroundingFunction() will execute. 

Note that we can also define our own label or name tag. 

In the code above, we defined our custom label called myLabel@ and then specified it for the return keyword. The @forEach label generated by the compiler for the forEach function is no longer available because we have defined our own. 

However, you'll soon see how this local return problem can be solved without labels when we discuss anonymous functions in Kotlin shortly.

3. Member Functions

This kind of function is defined inside a class, object, or interface. Using member functions helps us to modularize our programs further. Let's now see how to create a member function.

This code snippet shows a class Circle (we'll discuss Kotlin classes in later posts) that has a member function calculateArea(). This function takes a parameter radius to calculate the area of a circle.

To invoke a member function, we use the name of the containing class or object instance with a dot, followed by the function name, passing any arguments if need be.

4. Anonymous Functions

An anonymous function is another way to define a block of code that can be passed to a function. It is not bound to any identifier. Here are the characteristics of an anonymous function in Kotlin:

  • has no name
  • is created with the fun keyword
  • contains a function body

Because we passed a lambda to the last() function above, we can't be explicit about the return type. To be explicit about the return type, we need to use an anonymous function instead.

In the above code, we have replaced the lambda expression with an anonymous function because we want to be explicit about the return type. 

Towards the end of the lambda section in this tutorial, we used a label to specify which function to return from. Using an anonymous function instead of a lambda inside the forEach() function solves this problem more simply. The return expression returns from the anonymous function and not from the surrounding one, which in our case is surroundingFunction().

5. Local or Nested Functions

To take program modularization further, Kotlin provides us with local functions—also known as nested functions. A local function is a function that is declared inside another function. 

As you can observe in the code snippet above, we have two single-line functions: calCircumference() and calArea() nested inside the printCircumferenceAndAread() function. The nested functions can be called only from within the enclosing function and not outside. Again, the use of nested functions makes our program more modular and tidy. 

We can make our local functions more concise by not explicitly passing parameters to them. This is possible because local functions have access to all parameters and variables of the enclosing function. Let's see that now in action:

As you can see, this updated code looks more readable and reduces the noise we had before. Though the enclosing function in this example given is small, in a larger enclosing function that can be broken down into smaller nested functions, this feature can really come in handy. 

6. Infix Functions

The infix notation allows us to easily call a one-argument member function or extension function. In addition to a function being one-argument, you must also define the function using the infix modifier. To create an infix function, two parameters are involved. The first parameter is the target object, while the second parameter is just a single parameter passed to the function. 

Creating an Infix Member Function

Let's look at how to create an infix function in a class. In the code example below, we created a Student class with a mutable kotlinScore instance field. We created an infix function by using the infix modifier before the fun keyword. As you can see below, we created an infix function addKotlinScore() that takes a score and adds to the kotlinScore instance field. 

Calling an Infix Function

Let's also see how to invoke the infix function we have created. To call an infix function in Kotlin, we don't need to use the dot notation, and we don't need to wrap the parameter with parentheses. 

In the code above, we called the infix function, the target object is student, and the double 95.00 is the parameter passed to the function. 

Using infix functions wisely can make our code more expressive and clearer than the normal style. This is greatly appreciated when writing unit tests in Kotlin (we'll discuss testing in Kotlin in a future post).

The to Infix Function

In Kotlin, we can make the creation of a Pair instance more succinct by using the to infix function instead of the Pair constructor. (Behind the scenes, to also creates a Pair instance.) Note that the to function is also an extension function (we'll discuss these more in the next post).

Let's now compare the creation of a Pair instance using both the to infix function and directly using the Pair constructor, which performs the same operation, and see which one is better.

As you can see in the code above, using the to infix function is more concise than directly using the Pair constructor to create a Pair instance. Remember that using the to infix function, 234 is the target object and the String "Nigeria" is the parameter passed to the function. Moreover, note that we can also do this to create a Pair type:

In the Ranges and Collections post, we created a map collection in Kotlin by giving it a list of pairs—the first value being the key, and the second the value. Let's also compare the creation of a map by using both the to infix function and the Pair constructor to create the individual pairs.

In the code above, we created a comma-separated list of Pair types using the to infix function and passed them to the mapOf() function. We can also create the same map by directly using the Pair constructor for each pair.

As you can see again, sticking with the to infix function has less noise than using the Pair constructor. 

Conclusion

In this tutorial, you learned about some of the cool things you can do with functions in Kotlin. We covered:

  • top-level functions
  • lambda expressions or function literals
  • member functions
  • anonymous functions
  • local or nested functions
  • infix functions

But that's not all! There is still more to learn about functions in Kotlin. So in the next post, you'll learn some advanced uses of functions, such as extension functions, higher-order functions, and closures. See you soon!

To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts+!

  • Android SDK
    How to Use the Google Cloud Vision API in Android Apps
    Ashraff Hathibelagal
  • Java
    Android Design Patterns: The Observer Pattern
    Chike Mgbemena
  • Android SDK
    Adding Physics-Based Animations to Android Apps
    Ashraff Hathibelagal
  • Android SDK
    Android O: Phone Number Verification With SMS Tokens
    Chike Mgbemena
  • Android SDK
    What Are Android Instant Apps?
    Jessica Thornsby

No comments:

Post a Comment