We can store functions as mutable variables as well. If we wanted to make the previous example a mutable variable, we could update our variable declaration accordingly by replacing val with var as in the following code:
var greetingProvider: () -> String = { "Hello" }
Now, we can reassign the function value being stored within greetingProvider as we would with any other variable type:
fun main(args: Array<String>) {
println(greetingProvider())
greetingProvider = { "Hey" }
println(greetingProvider())
}
// output: Hello
// output: Hey
In this example, we've logged String provided by the original greetingProvider variable, then assigned a new function to greetingProvider and re-logged the resultant function invocation. In the resultant output, we can see that the printed value is updated once the function variable is updated.
Function variables follow the same nullability rules as any other type. This means we can define the nullable function types to use with our variables. The snippet demonstrates how we could update our greetingProvider variable to accept a null value:
var greetingProvider: (() -> String)? = null
A nullable function variable might be needed if you don't have a default function that can be assigned, or if the function is only optionally needed as with a click listener. By making the function type nullable, it affects how we can interact with that function variable.
Since greetingProvider is now a nullable type, we can no longer directly invoke the function using parentheses. The following snippet demonstrates the compiler error that results from this change:
fun main(args: Array<String>) {
val greeting = greetingProvider() // error: doesn't compile
}
To fix the code, we must now verify that the variable is non-null before invoking the function. The following snippet demonstrates two ways of verifying whether or not the function variable is null before invoking it:
fun main(args: Array<String>) {
greetingProvider?.invoke() // using elvis operator
greetingProvider?.let { it() } . // using non-null let call
}
In both of these cases, the compiler can verify whether or not the function variable is non-null and can safely call the function. This strict typing helps us ensure we don't encounter runtime exceptions, and it forces us to think about how we write our code to better ensure null-safety.