Kotlin | Function Type, Lambda and Higher-Order Functions
Functions in Kotlin can be stored in variables, passed as arguments to other functions and returned from other functions. However, since Kotlin is statically typed language, functions should have a type. In this post we will see how to declare the type of a function, how to use lambda to define a function and how to define a higher order function.
Function Type
- We know that a variable can have an explicit declared type or an implicit inferred type.
- The declared or inferred type is the type of the assigned or returned value.
val a:Int = 1 // explicit "Int" type
val b = "ABC" // inferred "Int" type
val c = SomeClass() // inferred "SomeClass" type
fun sum(a:Int, b:Int) = a + b // inferred return type "Int"
- Most often, the declared or inferred type is a class type.
- However, the declared or inferred type can also be a function type because in Kotlin, functions have types too!
- Unlike class type, function type is defined by its signature i.e. parameters and return type.
- Special notation is used to define the type of a function based on its parameters and return types.
- A function type is defined by listing the types of the input parameters between parentheses followed by an arrow
->
and ended by the return type. - For example,
(Int, Int) -> Boolean
is a function type representing all functions that take two arguments of typeInt
and returnBoolean
. - For example, the function below has a function type
(Int, Int) -> Boolean
.
fun gt(a: Int, b: Int): Boolean = a > b // function type: (Int,Int) -> Boolean
- Function type with now parameters can be written as following
() -> A
where A is a return type.
fun print(){ println("printing...") } // function type: () -> Unit
- Function type can also specify receiver type e.g.
Int.(Int) -> Boolean
represents a function that is called on an receiver object of typeInt
, takes one parameter of typeInt
and returnsBoolean
fun Int.gt(b: Int): Boolean = this > b // function type Int.(Int) -> Boolean
- Now, the function type can be used to declare variable type, parameter type or return type.
// variable with function type: (Int, Int) -> Boolean
val f: (Int, Int) -> Boolean = ...
// parameter with function type: (Int, Int)-> Int
fun someFunction(a:Int, f: (Int, Int) -> Int) { ... }
// return function of function type: () -> Unit
fun anotherFunction(): () -> Unit { ... }
typealias
- To improve code readability, function type can be named using
typealias
keyword.
typealias someType = (Int, Int) -> Boolean
val f: someType = ...
// this equavalent val f: (Int, Int) -> Boolean =
Lambda
- Lambda is a literal function which means it is not declared but passed as an expression.
- Lambda expression is always surrounded by curly braces
{...}
- Parameters types are optional if they can be inferred.
- Lambda’s body goes after the arrow
->
. - Similar to regular function, lambda has a function type.
{a:Int, b:Int -> a + b} // lambda of function type: (Int, Int) -> Int
// Equivalent function
fun sum(a:Int , b:Int) = a + b
- We can assign lambda to a variable of similar function type:
var sum: (Int, Int) -> Int = {a:Int, b:Int -> a + b}
- Which can be written without parameters types.
val sum: (Int, Int) -> Int = {a , b -> a + b}
println(sum(2,3)) // call sum
Special Case: Lambda with single parameter
- Lambda with single parameter is a special case.
-
The single parameter can be omitted along with the arrow
->
and useit
as a reference to the single parameter. - Regular way:
var increment: (Int) -> Int = { a -> a + 1 }
- Special case, single parameter is referenced as
it
var increment: (Int) -> Int = { it + 1 }
Where is return
in lambda?
- By default, the last expression of a lambda is implicitly returned.
val sum: (Int, Int) -> Int = {a , b ->
println("a = $a")
println("b = $b")
a + b // last expression is returned
}
Anonymous Functions
- Anonymous function is also a literal function which means it is not declared but passed as an expression.
- Anonymous function is a regular function without a name.
- Similar to regular functions, anonymous functions have function type.
var sum: (Int, Int) -> Int = fun(x: Int, y: Int): Int = x + y
println(sum(2,3)) // call sum
Creating & Calling Instances of a Function Type
- Similar to class type, we can create instances of a given function type.
- There are serval ways to create an instance of a function type.
-
For example, to create instances of
(Int, Int) -> Int
we can use one of the following ways: - Lambda
val sum: (Int, Int) -> Int = {a , b -> a + b}
- Anonymous function
var sum: (Int, Int) -> Int = fun(x: Int, y: Int): Int = x + y
- Callable Reference
fun sum(a: Int, b:Int) = a + b // top-level function
val sum: (Int, Int) -> Int = ::sum
class SomeClass{
fun sum(a: Int, b:Int) = a + b // member function
}
val sum: (Int, Int) -> Int = SomeClass()::sum
- Implementing function type
class SumFunctionType: (Int, Int) -> Int {
override operator fun invoke(a: Int, b:Int) = a + b
}
val sum: (Int, Int) -> Int = SumFunctionType()
Calling an instance of a function type
- Instances of a function type created using one of the ways above can be invoked using
invoke(a, b, ...)
function. - Or directly passing the parameter
(a, b, ...)
- For receiver type, the receiver should be the first argument.
val sum: (Int, Int) -> Int = {a , b -> a + b}
sum(2,3) // 5
sum.invoke(2,3) // 5
{a:Int, b:Int -> a + b}.invoke(2,3) // 5
{a:Int, b:Int -> a > b}(2,3) // 5
val f: Int.(Int) -> Int = { b -> this + b}
f.invoke(2,3) //5
f(2,3) // 5
2.f(3) //5
| Higher-Order Functions
- Higher-order functions can take functions as parameters or return a function.
- The type of the parameter accepting the function or the return type is declared using function type.
// Higher-Order Function
fun higherOrderSum(a:Int, b:Int, f: (Int, Int) -> Int): Int{
return f(a,b)
}
typealias someType = (Int, Int) -> Int
fun main() {
val lambdaSum: someType = {a , b -> a + b}
println(higherOrderSum(2, 3, lambdaSum)) // 5
}