Programming in Kotlin: Fundamentals

Aug 9 2022 · Kotlin 1.6, Android 12, IntelliJ IDEA CE 2022.1.3

Part 3: Functions & Nullability

23. Return Data From Functions

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 22. Write Custom Functions Next episode: 24. Challenge: Work with Functions

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 23. Return Data From Functions

Sometimes, you might want a variable or constant to be a result of a computation. This computation might require different data and resources and doing this directly in a variable might not be possible. For this, you do this computation inside a function. But this time around a function that returns a value.

Here’s the syntax for a function that returns a value. After the closing parenthesis of the function parameters, you add the return type. Then the last statement inside the body would be the data that is returned. This data must be preceded with the return keyword.

If you followed along and built the Bullseye app, you might remember writing functions like differenceAmount() or pointsForCurrentRound().

Both of those methods returned a value.

When a function or method returns a value, once the function is called, that value can be stored or used in-place.

pointsForCurrentRound() calculated and returned the point for the current round.

And differenceAmount() subtracted the sliderValue from a target value and returned it.

In earlier episodes, you created lots of ranges and used loops to iterate over them.

But creating ranges can be done in different ways, and that’s a good use case for a function. Let’s create a function for ranges.

You’ll start by creating the bare bones. Enter the following code:

fun createRange(start: Int, end: Int): IntRange {
  return start..end
}

This is the simplest version of this function. You can see it takes in two parameters: the start and the end of the range. Then it returns a range created with these parameters and it uses the return statement to do this.

You can also see the return type of this function is an IntRange. Which means that this function must return an IntRange, else, you’ll have a compile-time error.

Let’s create a range using it and print out the elements using a for loop. Add in the following code:

val closedRange = createRange(1, 10)

for (number in closedRange) print("$number \t")
println()

Run the project, to see the output.

Cool!!! The range is printed out

You’ll be printing out ranges in this episode so this would be a good piece of code to refactor into a function.

Create a function directly below the createRange function and move the code into it like so:

fun printRange(range: IntRange) {
  for (number in range) print("$number \t")
  println()
}

This function has one parameter which is the range to print. You changed it from closedRange to a more generic name called range and this value is what you defined in the parameter. This makes this function reusable for different Int ranges.

Also, notice that the function has no return type.

But behind the scene, if a function doesnt return anything then it returns a Unit by default. A Unit type is the equivalent to the void type in Java and it simply means nothing. So the printRange function returns nothing.

You can add it as the return of the printRange function like so: ): Unit Or you can remove the entire return type, as by default, Unit is the return type.

Now go ahead and replace the previous print code with the function like so:

val closedRange = createRange(1, 10)
// New code below
printRange(closedRange)

Now this is much better!!! Run the project, to see the output didn’t change.

Functions represent tasks or actions. They’re bits of code that do something.

That action can be represented by using a verb as the function name. printRange() prints the values from a range in the run panel.

We have some methods from the Bullseye game that uses this naming convention. The startNewGame() method starts a new game while the showResult() method shows the score on a pop-up.

They dont return any value but instead just perform an action and ends its execution.

When functions return a value, their names can be a noun. And if you recall the method from Bullseye, differenceAmount() and pointsForCurrentRound() are named for the values they return.

When you call those methods, you get the amount off or the points for the current round.

Naming functions or methods for the values they return is a common convention.

It is not a rule as you can see, we didnt follow it when naming the createRange function. Even though it returned a value, its name was not a noun. The verb createRange made more sense in this case.

If you’re working with a team, they may have naming conventions for you to follow. If you’re your own boss, how you name your functions is another stylistic decision you have to make. In either case, staying consistent can make it easier to read and reason about your code in the future.

There are different types of ranges though as we learned in the second part of this course. What if you want a range to be half open? Remember, a half open range exclude the last number in the given range. Let’s add a Boolean to createRange() to determine if it should return a half open or closed range.

Update the function to the following:

fun createRange(start: Int, end: Int, isHalfOpen: Boolean = false): IntRange {
  if (isHalfOpen) {
    return start until end
  } else {
    return start..end
  }
}

You can see the isHalfOpen parameter has a default value of false which would be used if you dont pass in the argument when calling the function.

Notice we have two return calls in the if statement. You can improve this behavior. Since if statements are expressions, you can simply return an expression here. And the IDE already hints us about this.

Go ahead and hover over the if statement. Then select the “Lift return out of if” action. And your code is updated accordingly.

fun createRange(start: Int, end: Int, isHalfOpen: Boolean = false): IntRange {
  return if (isHalfOpen) {
    start until end
  } else {
    start..end
  }
}

Next, create a half open range, and print it out:

val halfOpenRange = createRange(5, 10, true)
printRange(halfOpenRange)

Run the project, to see the new range printed out. And you can see that the number 10 is excluded from the range.

Do note that the closedRange would still return the previous range because the default value of isHalfOpen is false.

Instead of using the regular block of code with a return statement, you can turn this entire function to an expression. To do this, click on the return statement. Then click the bulb icon. And select “Convert to expression body”

fun createRange(start: Int, end: Int, isHalfOpen: Boolean = false): IntRange = if (isHalfOpen) {
    start until end
  } else {
    start..end
  }

And your code updates accordingly. By using an expression the same way you use it for values and if/else or when statements, you can return an expression for the function. And thats what you just did here.

Notice there is no return statement here. It’s not needed as you have the assignment operator.

You can even go one step further, and remove the return type, because, as you know, Kotlin can infer it from the expressions:

fun createRange(start: Int, end: Int, isHalfOpen: Boolean = false) = if (isHalfOpen) {
    start until end
  } else {
    start..end
  }

This is now much better than the original, right! :]

Run the project, to see the output doesn’t change!

But what if you wanted to return a different type of range, and keep the same name of the function? This is called overloading. Let’s see how it works!

To overload a function to return a different type of a range, copy and paste the createRange() function. Then update it to the following:

fun createRange(start: Char, end: Char, isHalfOpen: Boolean = false) = if (isHalfOpen) {
    start until end
  } else {
    start..end
  }

This function now accepts characters and creates a range of character symbols.

When you use it, pass in characters instead of integers, and it should work the same.

Notice how when you type out the name of the function, you get two of the same functions, but different parameters or return types:

val charRange = createRange('A', 'Z')
printRange(charRange)

You have an error on the printRange function and this is because it cannot print the range of characters because it was coded to print only int ranges. However, you can overload it to work with a character range too!

Copy and paste the printRange function. Then update it to the following:

fun printRange(range: CharRange) {
  for (character in range) print("$character \t")
  println()
}

Run the project, to see the character range!

The rule of thumb for overloading functions is this. The overloaded function must have:

  • A different number of parameters, OR
  • Different parameter types.

Do note that the return type alone is not enough to distinguish two functions. The compiler must still be able to tell the difference between these functions within a given scope

It’s worth noting that overloading should be used with care. Only use overloading for functions that are related and similar in behavior just like we’ve done in this episode.