To represent functions as objects, we need a type to represent them. A type specifies what we can do with an object[^02_3], for instance by specifying what methods[^02_2] and properties it has. A function type is a type that specifies that an object needs to be a function. We can call this function using the invoke method. However, functions can have different parameters and result types, so there are many possible function types.
Defining function types
A function type starts with a bracket, inside which it specifies the parameter types, separated with commas. After the bracket, there must be an arrow (->) and the result type. Since all functions in Kotlin need to have a result type, a function that does not return anything significant should declare Unit[^02_1] as its result type.
Here are a few function types (in the next chapters, we will see them in use):
() -> Unit - the simplest function type, representing a function that expects no arguments and returns nothing significant[^02_5].
(Int) -> Unit - a function type representing a function that expects a single argument of type Int and returns nothing significant.
(String, String) -> Unit - a function type representing a function that expects two arguments of type String and returns nothing significant.
() -> User - a function type representing a function that expects no arguments and returns an object of type User.
(String, String) -> String - a function type representing a function that expects two arguments of type String and returns an object of type String.
(String) -> Name - a function type representing a function that expects a single argument of type String and returns an object of type Name.
Functions that return Boolean, like (T) -> Boolean, are often named predicate. Functions that transform one value to another, like (T) -> R, are often called transformation. Functions that return Unit, like (T) -> Unit, are often called operation.
Using function types
A function type offers only one method: invoke. Its parameters and result type are the same as defined by the function type.
fun fetchText(
onSuccess: (String) -> Unit,
onFailure: (Throwable) -> Boolean
) {
// ...
onSuccess.invoke("Some text") // returns Unit
// or
val handled: Boolean =
onFailure.invoke(Error("Some error"))
}
Since invoke is an operator[^02_4], we can "call an object" that has this method. This is an implicit invoke call.
fun fetchText(
onSuccess: (String) -> Unit,
onFailure: (Throwable) -> Boolean
) {
// ...
onSuccess("Some text") // returns Unit
// or
val handled: Boolean = onFailure(Error("Some error"))
}
You can decide for yourself what approach you prefer. Explicit invoke calls are more readable for less experienced developers. An implicit call is shorter and, from a conceptual perspective, it better represents calling an object.
If a function type is nullable (in such a case, wrap it with a bracket and add a question mark at the end), you can use a safe call only with an explicit invoke.
Such names are visible in IntelliJ hints, and they have suggested names when we define a lambda expression for this type.
Named parameters are only for developers’ convenience, but they are not necessary from the technical point of view. However, it is good practice to add them if the parameters' meaning is unclear.
Type aliases
Function types can be long, especially when we use named arguments. In general, long types can be problematic, especially if they are repeated. Think of the setListItemListener example, where the same function type is repeated in the listener property and the removeListItemListener function.
private var listeners =
emptyList<(Int, Int, View, View) -> Unit>()
fun setListItemListener(
listener: (
position: Int, id: Int,
View, parent: View
) -> Unit
) {
listeners = listeners + listener
}
fun removeListItemListener(
listener: (Int, Int, View, View) -> Unit
) {
listeners = listeners - listener
}
We define a type alias with the typealias keyword. We then specify a name, followed by the equals sign (=), and we then specify which type should stand behind this name. Defining a type alias is like giving someone a nickname. It is not really a new type: it’s just a new way to reference the same type. Both types can be used interchangeably because types generated with type aliases are replaced with their definitions during compilation.
typealias Users = List<User>
fun updateUsers(users: Users) {}
// during compilation becomes
// fun updateUsers(users: List<User>) {}
fun main() {
val users: Users = emptyList()
// during compilation becomes
// val users: List<User> = emptyList()
val newUsers: List<User> = emptyList()
updateUsers(newUsers) // acceptable
}
Type aliases can help us resolve name conflicts across libraries. For example, instead of the following code[^02_7]:
import thirdparty.Name
class Foo {
val name1: Name
val name2: my.Name
}
We could use a type alias:
import my.Name
typealias ThirdPartyName = thirdparty.Name
class Foo {
val name1: ThirdPartyName
val name2: Name
}
Be careful because type aliases do not protect our types from misuse. If you define different names for the same type, they can all be used interchangeably[^02_6].
// DON'T DO THAT! Misleading and false type safety
typealias Minutes = Int
typealias Seconds = Int
fun decideAboutTime(): Minutes = 10
fun setupTimer(time: Seconds) {
/*...*/
}
fun main() {
val time = decideAboutTime()
setupTimer(time)
}
A function type is an interface
Under the hood, all function types are just interfaces with generic type parameters. This is why a class can implement a function type.
class OnClick : (Int) -> Unit {
override fun invoke(viewId: Int) {
// ...
}
}
fun setListener(l: (Int) -> Unit) {
/*...*/
}
fun main() {
val onClick = OnClick()
setListener(onClick)
}
We have learned something about function types, but we still do not know how to create objects of these types. This is what the following four chapters will be about, and we will start with the way that is the simplest, the oldest, and at the same time, the most forgotten: anonymous functions.
[^02_1]: Unit is an object with a single value that can be used within generic types. A function with the return type Unit is equivalent to a Java method that declares void.
[^02_2]: A method is a function associated with a class; it is called on an object, so both member and extension functions are methods.
[^02_3]: More about types in Kotlin Essentials, Typing system chapter.
[^02_4]: More about operators in Kotlin Essentials, Operators chapter.
[^02_5]: Those who have read the Typing system chapter from Kotlin Essentials might have guessed why I describe Unit as "nothing significant" instead of "nothing". Functions in Kotlin can indeed return nothing; in such cases, they declare Nothing as a result type, but this has a very different meaning than Unit.
[^02_6]: Protecting ourselves from type misuse is better described in Effective Kotlin, Item 52: Consider using inline value classes.
[^02_7]: The example was proposed by Endre Deak.
Marcin Moskala is a highly experienced developer and Kotlin instructor as the founder of Kt. Academy, an official JetBrains partner specializing in Kotlin training, Google Developers Expert, known for his significant contributions to the Kotlin community. Moskala is the author of several widely recognized books, including "Effective Kotlin," "Kotlin Coroutines," "Functional Kotlin," "Advanced Kotlin," "Kotlin Essentials," and "Android Development with Kotlin."
Beyond his literary achievements, Moskala is the author of the largest Medium publication dedicated to Kotlin. As a respected speaker, he has been invited to share his insights at numerous programming conferences, including events such as Droidcon and the prestigious Kotlin Conf, the premier conference dedicated to the Kotlin programming language.
Owen has been developing software since the mid 1990s and remembers the productivity of languages such as Clipper and Borland Delphi.
Since 2001, He moved to Web, Server based Java and the Open Source revolution.
With many years of commercial Java experience, He picked up on Kotlin in early 2015.
After taking detours into Clojure and Scala, like Goldilocks, He thinks Kotlin is just right and tastes the best.
Owen enthusiastically helps Kotlin developers continue to succeed.
Software architect with 15 years of experience, currently working on building infrastructure for AI. I think Kotlin is one of the best programming languages ever created.