Skip to main content

ยท 4 min read
MamboBryan

Overviewโ€‹

Lambda expressions in full, play a very important role in the day-to-day development cycle of any Kotliner. This article gets you familiarised with what they are and how to use them to better enhance your developer experience.

Definitionโ€‹

A lambda expression is a function literal .

But what is a function literal?
It is a function that is not declared but it is passed immediately as an expression.

// Declared
fun add(a: Int, b: Int) = a + b

// Lambda
val sum = { a: Int, b:Int -> a + b }

In the example above add is a declared and named function while sum is assigned to an expression, that is a function literal a.k.a lambda.

What is the syntax of a lambda expression?

Syntaxโ€‹

val isEven : (Int) -> Boolean = { number: Int -> 
// body
number % 2 == 0
}

The lambda expression in the above code is enclosed with curly braces {} .

  • After the curly braces, you declare a parameter
  • The body of the expression comes after ->
  • The last line of the expression will be treated as the return value.

NOTE: If the last line is not a value the expression will not return any value and the return type will be of Unit.

Show me some examples ๐Ÿ˜‰๐Ÿ˜‰

Examplesโ€‹

  • Full Syntax (all types declared)
    val addTwo : (Int, Int) -> Int = { a:Int, b:Int -> a + b }
    println(addTwo(1, 2))
    // 3
    println(addTwo.invoke(3,4))
    // you can also call it using invoke
    // 7
  • Omitting type declarations
    val addTwo = { a:Int, b:Int -> a % 2 == 0 }
    println(addTwo(1, 3))
    // 3
  • Without return type
    val addTwo = { a:Int, b:Int -> println("${a + b}") }
    println(addTwo(1,3))
    // 3
    // Kotlin.Unit
    In the function above it prints 3 but also prints the return value of the function which is Unit
  • As a function parameter
    fun String.takeIfOrNull (block : (String) -> Boolean) : String? {
    return if(block(this)) this else null
    }
    println("Hello".takeIfOrNull({ value -> value.length > 3 }))
    // Hello
    println("Kot".takeIfOrNull({ value -> value.length > 3 }))
    // null
    In the function above it prints Hello since the passed lambda expression returns true. But we can write the call for takeIfOrNull even simpler. How?
  • Trailing lambdas
    println("Hello".takeIfOrNull { value -> value.length > 3 })
    // Hello
    If the last parameter of a function is a function then the passed lambda can be placed outside the brackets. If the function takes only a function then the brackets can be completely omitted like in the example above. Could we remove more boilerplate? YES!
  • Accessing parameter values
    println("Hello".takeIfOrNull { it.length > 3 })
    // Hello
    If there is only one parameter the compiler can help us omit the parameter declaration and the arrow (->). We can thus access the parameter under the name it.

Caveatsโ€‹

  • Implicit parameter it of enclosing lambda is shadowed

Sometimes you might have a lambda inside another lambda, when this happens it's good to specify the parameter name to avoid shadowing it.

// Before
listOf(1,2,3).map {
it.toString() + listOf(3,5,7).map { it.plus(1) }
}

// After
listOf(1,2,3).map {
it.toString() + listOf(3,5,7).map { value -> value.plus(1) }
}
  • Unused variables

In some cases the expression can have more than one parameter but you only want to use one. You can replace the name with an underscore to show it's unused.

// Before
listOf(Pair(true, 1),Pair(true, 2)).map { (bool, number) ->
number + 1
}

// After
listOf(Pair(true, 1),Pair(true, 2)).map { (_, number) ->
number + 1
}
  • Returning values

In other cases, you may want to specify the return value of a lambda expression. In such cases using the return keyword would suffice but ensure you specify the name of the lambda to avoid exiting the parent function call.

// Before
listOf(Pair(true, 1),Pair(true, 2)).map { (bool, number) ->
if(bool) return number
val value = ...
...
return value
}

// After
listOf(Pair(true, 1),Pair(true, 2)).map { (bool, number) ->
if(bool) return@map number
val value = ...
...
return@map value
}

Conclusionโ€‹

With that you can try the inbuilt lambdas provided by Kotlin or create your own, just remember the caveats. See you in the next article!

ยท 4 min read
MamboBryan

You've started out in Kotlin or you've been already using it for some time now, but still feel like you aren't using the power of high-order functions to the max. Well, this is the article for you! Let's briefly focus on high-order functions and how they can help you in your development process.

Definitionโ€‹

Any function that can take a function as a parameter or return a function

Kotlin treats it functions as first class citizens and as such they can be assigned to variables, stored in data structures and even passed to other functions. Any function that can take a function as an argument is classified as a high order function.

Syntaxโ€‹

fun build( block : (parameter: ParameterType) -> ReturnType ){}

In the syntax above build is our high order function. Let's breakdown it's contents :

blockโ€‹

This is the parameter name for the build function. Notice the explicit type of this parameter is not a literal or an object but a function represented as () -> Unit.

parameter : ParameterTypeโ€‹

Since block is a function it can take a parameter/s.

  • leave it empty if no parameter is required : block : () -> ReturnType
  • the parameter name is used for and just pass the type : block : (Type) -> ReturnType
ReturnTypeโ€‹

This is the return type of the block function. Since it cannot be inferred it should be explicitly specified. If the function doesn't return any value the you're required to explicitly specify it as Unit. This can be represented as block : () -> Unit

We use the function by either block.invoke() or block()

With that let's see some examples and common use cases.

Examplesโ€‹

without argumentsโ€‹

// declaration
fun hello( block : () -> Unit ) {
block.invoke() // also block()
}
// usage
fun main() {
hello( { println("Jambo Kotliner") } )
}
// output
// Jambo Kotliner

with argumentsโ€‹

// declaration
fun hello( block : (s:String) -> Unit ) {
block.invoke("Jambo Kotliner") // also block("Jambo Kotliner")
}
// usage
fun main() {
hello( { greeting -> println(greeting) } )
}
// output
// Jambo Kotliner

as a return valueโ€‹

// declaration
fun hello() : () -> Unit {
return { println("Jambo Kotliner") }
}
// usage
fun main() {
val greet = hello()
greet.invoke() // also greet()
}
// output
// Jambo Kotliner

Usecaseโ€‹

One of the most common use cases for a custom high order function is when making network requests. There are a number of exceptions that can be thrown during any of these requests and without properly handling them our app might crash and fail. This can be prevented using high order functions. Let's look at what's going on under the hood.

data class NetworkResult<T> (
val success : Boolean,
val message : String,
val data : T?
)

suspend fun <T> safeApiCall( request : suspend () -> NetworkResult<T?> ) : NetworkResult<T?> {
return try {
val response = request.invoke()
NetworkResult(success = true, message = "success", data = response)
} catch (e : Exception) {
NetworkResult(success = false, message = e.localizedMessage, data = null)
}
}

The safeApiCall is our high order function which takes a suspended function request as a parameter. IN the function body we try to run request (this is the network request) and catch any exceptions thrown, which we can map it to our NetworkResult class avoiding any crashes at runtime.

Conclusionโ€‹

With these few tips and tricks you're ready to start using high order functions in your development process. But with great power comes great responsibility and for this case be careful not to use to many of them, they might introduce runtime overhead if you're not careful. Feel free to try it out yourself and leave a comment down below on your thoughts and what you'd like to know next. See you in the next article!