In the last chapter, we wrote some Kotlin code to calculate the circumference of individual circles. Once we start calculating the circumference of many circles of different sizes, we can easily end up writing the same code over and over again.
In this chapter, we’re going to write functions, which will make it easy to calculate the circumference of any circle—without repeating ourselves!
Introduction to Functions
In the last chapter, we saw that it’s easy to calculate the circumference of a circle.
We also saw how easy it is to write Kotlin code to calculate it for us.
val pi = 3.14 var radius = 5.2 val circumference = 2 * pi * radiusThe code above calculates the circumference of a circle that has a radius of 5.2. But of course, not all circles have a radius of 5.2! What happens if we also want to determine the circumference of a circle that has a radius of 6.7? Or 10.0?
Well, we could just write out the equation multiple times.
val pi = 3.14 var radius = 5.2 val circumferenceOfSmallCircle = 2 * pi * radius radius = 6.7 val circumferenceOfMediumCircle = 2 * pi * radius radius = 10.0 val circumferenceOfLargeCircle = 2 * pi * radiusThis certainly works, but wow—look at how we had to type the same thing over and over again!
When we have the same code over and over again like this, we call it duplication. In most cases, duplicated code is bad because:
- When we type the same thing so many times, it becomes more likely that we might type it wrong in one of those cases. For example, one time, we might accidentally type
3 * pi radius.- If we want to change the equation, we’d have to find all of the places where we typed it, and make sure to update each one of them.
- It can be more difficult to read the code when our eyes see the same thing written over and over again on the screen.
Let’s change our code so that we only have to write
2 * pi * radiusone time, and then use that to calculate the circumference of any circle. In other words, let’s remove the duplication!Removing Duplication with Functions
Even though we wrote
2 * pi * radiusthree times, the only thing that actually changed each time was theradius. In other words,2never changed, andpinever changed (it equaled3.14each time). But the value ofradiuswas different each time: first5.2, then6.7, and then10.0.Since the radius is the only thing that changes each time, it would be amazing if we could just convert any radius into a circumference. In other words, what if we could build a machine where we insert a radius on one side, and a circumference pops out of the other side?
- Since we’re putting in a radius, we call that the input.
- And since a circumference comes out of the other side, we call that the output.
Now, we won’t be creating a real machine, but instead, we will create a function, which will do exactly what we want—we’ll give it a radius and get a circumference back from it!
Function Basics
Creating a Function
Here’s how we can write a simple function in Kotlin.
This looks like a lot, but there are really just a few pieces, and they’re all easy to understand.
- First,
funis a keyword (just likevalandvarare keywords). It tells Kotlin that we are writing a function.- Next,
circumferenceis the name of our function. Much like variable names, we can name a function just about anything we want, likecircumference,getCircumference,circ, or even justc.- Then,
(radius: Double)says that this function has an input calledradius, which has a type calledDouble. We callradiusa parameter of this function.- Then, the
": Double"after the closing parenthesis indicates that the output of the function will be aDoublevalue. This is called the return type of the function.- And finally,
2 * pi * radiusis called the body of the function. Whatever this expression evaluates to will be the output of the function. The value of that output is referred to as the result of the function. It’s the thing that comes out of the machine. Note that whatever this expression evaluates to must be the same type that’s specified by the return type. In this example,2 * pi * radiusmust evaluate to aDoubleor we’ll get an error.In the image below, we can compare our function with the machine we imagined earlier.
As mentioned in the last chapter, when we declare a variable, we often don’t have to specify its type explicitly. Instead, we can let Kotlin use its type inference.
fun circumference(radius: Double) = 2 * pi * radiusKotlin programmers often use type inference for simple functions like this one. By the end of this chapter, we’ll see some more complex functions where we will have to specify the return type explicitly.
And now that we’ve created our function, it’s time to use it!
Calling a Function
When we use the function—that is, when we put something into the machine—it’s referred to as calling or invoking the function. The place where it is called is known as the calling code or the call site.
Here’s how we can call a function in Kotlin.
First,
circumferenceOfSmallCircleis a variable that will hold the result of the function call (that is, whatever comes out of the machine).Next,
circumferenceis the name of the function that we’re calling.The value
5.2is the argument of this function—it’s the thing that we’re putting into the machine. When we call a function with an argument, we sometimes say that we are passing the argument to the function.What happens when we call a function? Let’s say we wrote a function and called it, like this.
fun circumference(radius: Double) = 2 * pi * radius val circumferenceOfSmallCircle = circumference(5.2)When we call that function, it’s kind of like we’re just dropping the body of the function—
2 * pi * radius—right where we see the function call—circumference(5.2). So we can imagine that it looks like the next code listing.fun circumference(radius: Double) = 2 * pi * radius val circumferenceOfSmallCircle = 2 * pi * radiusThen, since we passed
5.2as theradius, we could imagine substituting the value5.2where we had dropped inradius.fun circumference(radius: Double) = 2 * pi * radius val circumferenceOfSmallCircle = 2 * pi * 5.2So, when we call
circumference(5.2), it’s as if we had written2 * pi * 5.2at the same spot.Now that we’ve got a function that can calculate the circumference from a radius, we can call that function as many times as we need!
val pi = 3.14 fun circumference(radius: Double) = 2 * pi * radius val circumferenceOfSmallCircle = circumference(5.2) val circumferenceOfMediumCircle = circumference(6.7) val circumferenceOfLargeCircle = circumference(10.0)This looks so much cleaner than our original code! Because we have a function, we don’t need to write
2 * pi * radiusover and over! Instead, we just call thecircumference()function once for each circle.Arguments and Parameters: What’s the difference?
It’s easy to confuse an argument with a parameter, so it’s important to understand the distinction:
An argument is a value that we pass to the function. For example, when we pass
5.2into thecircumference()function, the argument is5.2.A parameter is effectively a variable inside the function that will hold that value. For example, when we pass
5.2tocircumference(), it is assigned to the parameter namedradius.An argument is a value that usually comes from outside of the function, whereas a parameter is declared inside the function. As a mnemonic, simply associate the word “argument” with the word “outside”, as shown in this cartoon.
Functions with More Than One Parameter
The
circumference()function above has just one parameter, namedradius, but there are times when we might need a function that has more than that. Let’s create a function that has two parameters!Even if physics class was a while ago, you probably know how to calculate speed. It’s easy to remember, because we say it aloud all the time—“Your speed is 100 kilometers per hour”.
Speed—e.g., “Kilometers per hour”—is just distance (“kilometers”) divided by (“per”) time (“hour”).
In Kotlin, you use a forward slash to represent division. You can think of it as a fraction that fell over to the left:
To start with, let’s just write some simple code to calculate the average speed of a car that has traveled 321.8 kilometers in 4.15 hours.
val distance = 321.8 val time = 4.15 val speed = distance / timeNow, let’s turn that
distance / timeexpression into a function. In order to create a function for speed, we need to know two things:distanceandtime. In Kotlin, when we need a function with two parameters, we can just separate those parameters with a comma.fun speed(distance: Double, time: Double) = distance / timeWhen we need to call this function, the arguments are also separated with a comma. For example, we can call the
speed()function with the same values as we used above, separating those values with a comma.val averageSpeed = speed(321.8, 4.15)The result is approximately
77.54kilometers per hour.Note that the arguments here are in the same order as the parameters.
- Since
321.8is the first argument,321.8will be assigned to the first parameter, which isdistance.- Since
4.15is the second argument,4.15will be assigned to the second parameter, which istime.In other words, the position of the argument matters when we call a function this way, which is why we refer to arguments like these as positional arguments.
But this isn’t the only way to pass arguments to a function!
Named Arguments
Rather than relying on the position of the arguments, we can instead use the name of the parameter, like this.
val averageSpeed = speed(distance = 321.8, time = 4.15)These are called named arguments. The neat thing about named arguments is that the order doesn’t matter. So, we can call the function like this, with the arguments in a different order.
val averageSpeed = speed(time = 4.15, distance = 321.8)In fact, in the code below, all five function calls will end up with the exact same result.
val averageSpeed1 = speed(321.8, 4.15) val averageSpeed2 = speed(distance = 321.8, 4.15) val averageSpeed3 = speed(321.8, time = 4.15) val averageSpeed4 = speed(distance = 321.8, time = 4.15) val averageSpeed5 = speed(time = 4.15, distance = 321.8)Default Arguments
In some cases, we might end up passing the same argument value to a function over and over again. For example, we might want to calculate how fast…
- One person is walking.
- Another person is biking.
- A third person is driving a car.
- A fourth person is flying a plane.
Everybody was moving for 2.0 hours, except for the plane, which got to its final destination in only 1.5 hours. Using our
speed()function from above, we can calculate their speeds like this.val walkingSpeed = speed(10.2, 2.0) val bikingSpeed = speed(29.6, 2.0) val drivingSpeed = speed(225.3, 2.0) val flyingSpeed = speed(1368.747, 1.5)Instead of having to pass
2.0for thetimeparameter over and over, we could make2.0a default argument when we define the function.Let’s update our
speed()function so that thetimeparameter defaults to2.0.fun speed(distance: Double, time: Double = 2.0) = distance / timeNow we can omit the argument for
timewhenever it should equal2.0.val walkingSpeed = speed(10.2) val bikingSpeed = speed(29.6) val drivingSpeed = speed(225.3) val flyingSpeed = speed(1368.747, 1.5)For walking, biking, and driving, we left off the
timeargument, so those defaulted to2.0. But for flying, we passed1.5. The results for Listing 2.16 are exactly the same as those for Listing 2.14.But what happens when we want a default argument for the first parameter instead of the second parameter?
When a Default Argument Comes First
Our walker, biker, driver, and pilot are at it again. But this time, it’s a race! Whoever gets to the finish line 42.195 kilometers away gets the prize.
Everyone finished the race, except for the airplane, which got a flat tire before it could get off the ground.
val walkingSpeed = speed(42.195, 8.27) val bikingSpeed = speed(42.195, 2.85) val drivingSpeed = speed(42.195, 0.37) val flyingSpeed = speed(0.12, 0.01)Since the distance is 42.195 for everyone except the airplane, let’s update our code—we’ll remove the default for
time, and instead give it a defaultdistanceof42.195.fun speed(distance: Double = 42.195, time: Double) = distance / timeNow, since the walker, the biker, and the driver all travel the same distance, we should be able to omit the value for the first parameter,
distance. It might be tempting to call the function as we’re doing in Listing 2.19 below, but it actually causes an error: No value passed for parameter ‘time’.val walkingSpeed = speed(8.27) val bikingSpeed = speed(2.85) val drivingSpeed = speed(0.37) val flyingSpeed = speed(0.12, 0.01)ErrorWhy did that happen?
Since we’re using a positional argument, we actually ended up omitting time instead of distance. In other words, we wanted to assign
8.27to thetimeparameter.But we actually assigned
8.27to thedistanceparameter, because8.27is the first argument, anddistanceis the first parameter.To tell Kotlin that we’re sending the
timeinstead, we simply use named arguments, like this.val walkingSpeed = speed(time = 8.27) val bikingSpeed = speed(time = 2.85) val drivingSpeed = speed(time = 0.37) val flyingSpeed = speed(0.12, 0.01)With these changes, when we call
speed()for walking, biking, and driving, the first parameter—distance—will default to42.195.Expression Bodies and Block Bodies
So far we’ve been writing functions that simply evaluate an expression.
- Our
circumference()function just evaluates2 * pi * radius.- Our
speed()function just evaluatesdistance / time.Let’s look at our code for
circumference()again.val pi = 3.14 fun circumference(radius: Double) = 2 * pi * radiusWhen we write a function this way, where the body is just a single expression, we say that the function has an expression body.
Kotlin also gives us a second way that we can write functions. Let’s rewrite the
circumference()function using this second way.val pi = 3.14 fun circumference(radius: Double): Double { return 2 * pi * radius }When we write a function this way, we say that it has a block body. Writing a function with a block body is a little more complex than writing it with an expression body, so let’s take a closer look at the new pieces.
First, notice the
: Doubleafter the parentheses. We were able to use type inference for expression bodies, but when we use a block body, we must specify the return type explicitly (with one exception, which we’ll see in a moment).Next, notice the opening and closing braces:
{and}. Everything between those two braces is referred to as a code block (which is why we call this a function with a block body!)Finally, notice the word
returninside that code block. The wordreturnis a keyword that tells Kotlin that the expression that follows it is what the function should return. As with expression body functions, the value that the function returns must match the type that we said the function returns!Clearly, we have to type more code when writing functions with a block bodies than those with an expression bodies, so why would we ever want to use them?
- They let us write more than one line of code in the function.
- They let us write statements inside them, not just expressions.
For example, so far we have defined
pioutside of our function, like in Listing 2.22. But it’d be great to define it inside the function instead.fun circumference(radius: Double): Double { val pi = 3.14 return 2 * pi * radius }By moving
pito the inside of the function like this, it will only be accessible from inside the function. In other words, if we try to use it outside of the function, we’ll get an error.fun circumference(radius: Double): Double { val pi = 3.14 return 2 * pi * radius } val tau = 2 * piErrorThroughout the rest of this book, we’ll see many examples of both kinds of functions—those with expression bodies and those with block bodies.
Functions without a Result
Sometimes we don’t need a result from our function. For example, let’s say we’ve got a variable named
counter, which keeps track of a number that increases over time, and a function namedincrement(), which increasescounterby one every time we call it. To increment thecountervariable, we can use the statementcounter = counter + 1. As we’ve seen, functions with expression bodies can only contain a single expression. We can’t use a statement in an expression body, so the following code won’t work.var counter = 0 fun increment() = counter = counter + 1ErrorInstead of using an expression body, let’s use a block body for this function instead.
var counter = 0 fun increment() { counter = counter + 1 }Did you notice that we didn’t specify a return type on this function?
You might be surprised to learn that, even though we didn’t specify a return type, and even though this function contains no expression, this function still returns a value.
That’s right! When we omit a return type for a function that has a block body, it automatically returns a special Kotlin type called
Unit.Unitisn’t a meaningful result—it’s not a number or a string, and you can’t really do much with it. But in Kotlin, every function technically returns something, even if it’s justUnit. This is actually useful because it ensures that every function call in Kotlin is an expression that can be evaluated.The
main()functionWe’ve created a variety of functions throughout this chapter, and each time, we were able to name the function whatever we wanted. However, if we name our function
main(), then it’ll be regarded as a special function that acts as the front door, or entry point, to the rest of our code.For example, we can create a
main()function that will call thecircumference()function three times, each time with a different value.val pi = 3.14 fun circumference(radius: Double): Double { return 2 * pi * radius } fun main() { var c = circumference(0.75) c = circumference(3.8) c = circumference(1.0) }If you’re writing your Kotlin code in IntelliJ IDEA, you’ll probably see a green play icon to the left of the
main()function.When you click on that, you can run the code inside the
main()function.By the end of the
main()function in Listing 2.27, the value of thecvariable will be6.28. However, as things currently are, we don’t have a way to actually see the value! To address this, let’s start printing things to the screen!Printing to the Screen
As we’ve seen, we can create our own functions, but Kotlin also includes many functions that are ready for you to use! The functions that Kotlin provides are all bundled together as part of its standard library. In fact, the types that we’ve seen so far—
Double,Int,Boolean, andString—all come from the standard library, too!To print out a message to the screen, we’ll use the
println()function from the standard library. This function is pronounced “print line”, because it prints a line of text to the screen. We can pass it any kind of object, and Kotlin will do its best to write out information about that object. But it’s most common to just give it a string.Here’s a simple example demonstrating how we can use it.
fun main() { println("This message will be printed on the screen.") }When we run this, we’ll see the string printed out on the screen.
This message will be printed on the screen.We can also include an expression inside the string. When the string is evaluated, that expression will be evaluated, and its result will be inserted. For example, we can print the circumference of a circle.
println("The circumference is ${circumference(1.0)}")This feature is called string interpolation, and a string that includes an expression is called a templated string. To add an expression to your string, include a dollar sign
$, an opening brace{, the expression to evaluate—e.g.,circumference(1.0)—, and a closing brace}. In the code above,circumference(1.0)will be evaluated to6.28, so the final string on the screen will look like this:The circumference is 6.28When the expression is simply a variable name, we can omit the braces—just use a dollar sign and the name of the variable. For example, we could print out both the radius and the circumference.
val radius = 1.0 println("The radius is $radius and the circumference is ${circumference(radius)}")The
println()function is one of the most helpful functions to know when learning Kotlin, because we can use it to inspect the value of a property or the result of a function call. Whenever your code isn’t doing what you think it should be doing, you can useprintln()to help figure out why—in other words, it’s a simple way to debug your code. For example, we can use it in ourmain()function to see what the circumference is at each step along the way.fun main() { var c = circumference(0.75) println(c) c = circumference(3.8) println(c) c = circumference(1.0) println(c) }Note that, although you typically need to use a
main()function to run your code, the code listings throughout this book will usually omit themain()function for brevity and focus. Instead, they’ll simply include the contents of themain()function directly. For example, instead of this…fun main() { var c = circumference(0.75) c = circumference(3.8) c = circumference(1.0) }… the code listings will usually only look like this.
var c = circumference(0.75) c = circumference(3.8) c = circumference(1.0)If you’re ever in doubt, simply consult the online source code for this book.
Summary
With a solid understanding of functions, we no longer need to repeat the same expressions over and over—we can just call the same function with different arguments! In this chapter, we covered a lot, including:
- What a function is, and how it can help us remove duplication from our code.
- What a function parameter is, and how it differs from an argument of a function call.
- The difference between positional and named arguments.
- How to give your arguments default values.
- The difference between expression bodies and block bodies.
- How Kotlin will use the
Unittype if we have no meaningful result to return from a function.- How the
main()function acts as an entry point to the rest of our code.- How we can use the
println()function from the standard library.- How to use string interpolation to evaluate expressions directly within a string.
Next up, we’ll shift gears to learn about conditionals—giving our code the ability to make decisions and adapt to different situations.
Thanks to Louis CAD, James Lorenzen, Matt McKenna, and Charles Muchene for reviewing this chapter.